summaryrefslogtreecommitdiff
path: root/android/content/SyncRequest.java
blob: 74d2f114aae5c25c76e1df17425e4728c0375bb4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.content;

import android.accounts.Account;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;

/**
 * Convenience class to construct sync requests. See {@link android.content.SyncRequest.Builder}
 * for an explanation of the various functions. The resulting object is passed through to the
 * framework via {@link android.content.ContentResolver#requestSync(SyncRequest)}.
 */
public class SyncRequest implements Parcelable {
    private static final String TAG = "SyncRequest";
    /** Account to pass to the sync adapter. Can be null. */
    private final Account mAccountToSync;
    /** Authority string that corresponds to a ContentProvider. */
    private final String mAuthority;
    /** Bundle containing user info as well as sync settings. */
    private final Bundle mExtras;
    /** Don't allow this sync request on metered networks. */
    private final boolean mDisallowMetered;
    /**
     * Amount of time before {@link #mSyncRunTimeSecs} from which the sync may optionally be
     * started.
     */
    private final long mSyncFlexTimeSecs;
    /**
     * Specifies a point in the future at which the sync must have been scheduled to run.
     */
    private final long mSyncRunTimeSecs;
    /** Periodic versus one-off. */
    private final boolean mIsPeriodic;
    /** Service versus provider. */
    private final boolean mIsAuthority;
    /** Sync should be run in lieu of other syncs. */
    private final boolean mIsExpedited;

    /**
     * {@hide}
     * @return whether this sync is periodic or one-time. A Sync Request must be
     *         either one of these or an InvalidStateException will be thrown in
     *         Builder.build().
     */
    public boolean isPeriodic() {
        return mIsPeriodic;
    }

    /**
     * {@hide}
     * @return whether this sync is expedited.
     */
    public boolean isExpedited() {
        return mIsExpedited;
    }

    /**
     * {@hide}
     *
     * @return account object for this sync.
     * @throws IllegalArgumentException if this function is called for a request that targets a
     * sync service.
     */
    public Account getAccount() {
        return mAccountToSync;
    }

    /**
     * {@hide}
     *
     * @return provider for this sync.
     * @throws IllegalArgumentException if this function is called for a request that targets a
     * sync service.
     */
    public String getProvider() {
        return mAuthority;
    }

    /**
     * {@hide}
     * Retrieve bundle for this SyncRequest. Will not be null.
     */
    public Bundle getBundle() {
        return mExtras;
    }

    /**
     * {@hide}
     * @return the earliest point in time that this sync can be scheduled.
     */
    public long getSyncFlexTime() {
        return mSyncFlexTimeSecs;
    }
    /**
     * {@hide}
     * @return the last point in time at which this sync must scheduled.
     */
    public long getSyncRunTime() {
        return mSyncRunTimeSecs;
    }

    public static final Creator<SyncRequest> CREATOR = new Creator<SyncRequest>() {

        @Override
        public SyncRequest createFromParcel(Parcel in) {
            return new SyncRequest(in);
        }

        @Override
        public SyncRequest[] newArray(int size) {
            return new SyncRequest[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int flags) {
        parcel.writeBundle(mExtras);
        parcel.writeLong(mSyncFlexTimeSecs);
        parcel.writeLong(mSyncRunTimeSecs);
        parcel.writeInt((mIsPeriodic ? 1 : 0));
        parcel.writeInt((mDisallowMetered ? 1 : 0));
        parcel.writeInt((mIsAuthority ? 1 : 0));
        parcel.writeInt((mIsExpedited? 1 : 0));
        parcel.writeParcelable(mAccountToSync, flags);
        parcel.writeString(mAuthority);
    }

    private SyncRequest(Parcel in) {
        mExtras = Bundle.setDefusable(in.readBundle(), true);
        mSyncFlexTimeSecs = in.readLong();
        mSyncRunTimeSecs = in.readLong();
        mIsPeriodic = (in.readInt() != 0);
        mDisallowMetered = (in.readInt() != 0);
        mIsAuthority = (in.readInt() != 0);
        mIsExpedited = (in.readInt() != 0);
        mAccountToSync = in.readParcelable(null);
        mAuthority = in.readString();
    }

    /** {@hide} Protected ctor to instantiate anonymous SyncRequest. */
    protected SyncRequest(SyncRequest.Builder b) {
        mSyncFlexTimeSecs = b.mSyncFlexTimeSecs;
        mSyncRunTimeSecs = b.mSyncRunTimeSecs;
        mAccountToSync = b.mAccount;
        mAuthority = b.mAuthority;
        mIsPeriodic = (b.mSyncType == Builder.SYNC_TYPE_PERIODIC);
        mIsAuthority = (b.mSyncTarget == Builder.SYNC_TARGET_ADAPTER);
        mIsExpedited = b.mExpedited;
        mExtras = new Bundle(b.mCustomExtras);
        // For now we merge the sync config extras & the custom extras into one bundle.
        // TODO: pass the configuration extras through separately.
        mExtras.putAll(b.mSyncConfigExtras);
        mDisallowMetered = b.mDisallowMetered;
    }

    /**
     * Builder class for a {@link SyncRequest}. As you build your SyncRequest this class will also
     * perform validation.
     */
    public static class Builder {
        /** Unknown sync type. */
        private static final int SYNC_TYPE_UNKNOWN = 0;
        /** Specify that this is a periodic sync. */
        private static final int SYNC_TYPE_PERIODIC = 1;
        /** Specify that this is a one-time sync. */
        private static final int SYNC_TYPE_ONCE = 2;
        /** Unknown sync target. */
        private static final int SYNC_TARGET_UNKNOWN = 0;
        /** Specify that this is a sync with a provider. */
        private static final int SYNC_TARGET_ADAPTER = 2;
        /**
         * Earliest point of displacement into the future at which this sync can
         * occur.
         */
        private long mSyncFlexTimeSecs;
        /** Displacement into the future at which this sync must occur. */
        private long mSyncRunTimeSecs;
        /**
         * Sync configuration information - custom user data explicitly provided by the developer.
         * This data is handed over to the sync operation.
         */
        private Bundle mCustomExtras;
        /**
         * Sync system configuration -  used to store system sync configuration. Corresponds to
         * ContentResolver.SYNC_EXTRAS_* flags.
         * TODO: Use this instead of dumping into one bundle. Need to decide if these flags should
         * discriminate between equivalent syncs.
         */
        private Bundle mSyncConfigExtras;
        /** Whether or not this sync can occur on metered networks. Default false. */
        private boolean mDisallowMetered;
        /**
         * Whether this builder is building a periodic sync, or a one-time sync.
         */
        private int mSyncType = SYNC_TYPE_UNKNOWN;
        /** Whether this will go to a sync adapter. */
        private int mSyncTarget = SYNC_TARGET_UNKNOWN;
        /** Whether this is a user-activated sync. */
        private boolean mIsManual;
        /**
         * Whether to retry this one-time sync if the sync fails. Not valid for
         * periodic syncs. See {@link ContentResolver#SYNC_EXTRAS_DO_NOT_RETRY}.
         */
        private boolean mNoRetry;
        /**
         * Whether to respect back-off for this one-time sync. Not valid for
         * periodic syncs. See
         * {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF};
         */
        private boolean mIgnoreBackoff;

        /** Ignore sync system settings and perform sync anyway. */
        private boolean mIgnoreSettings;

        /** This sync will run in preference to other non-expedited syncs. */
        private boolean mExpedited;

        /**
         * The Account object that together with an Authority name define the SyncAdapter (if
         * this sync is bound to a provider), otherwise null.
         */
        private Account mAccount;
        /**
         * The Authority name that together with an Account define the SyncAdapter (if
         * this sync is bound to a provider), otherwise null.
         */
        private String mAuthority;
        /**
         * Whether the sync requires the phone to be plugged in.
         */
        private boolean mRequiresCharging;

        public Builder() {
        }

        /**
         * Request that a sync occur immediately.
         *
         * Example
         * <pre>
         *     SyncRequest.Builder builder = (new SyncRequest.Builder()).syncOnce();
         * </pre>
         */
        public Builder syncOnce() {
            if (mSyncType != SYNC_TYPE_UNKNOWN) {
                throw new IllegalArgumentException("Sync type has already been defined.");
            }
            mSyncType = SYNC_TYPE_ONCE;
            setupInterval(0, 0);
            return this;
        }

        /**
         * Build a periodic sync. Either this or syncOnce() <b>must</b> be called for this builder.
         * Syncs are identified by target {@link android.provider} and by the
         * contents of the extras bundle.
         * You cannot reuse the same builder for one-time syncs after having specified a periodic
         * sync (by calling this function). If you do, an <code>IllegalArgumentException</code>
         * will be thrown.
         * <p>The bundle for a periodic sync can be queried by applications with the correct
         * permissions using
         * {@link ContentResolver#getPeriodicSyncs(Account account, String provider)}, so no
         * sensitive data should be transferred here.
         *
         * Example usage.
         *
         * <pre>
         *     Request a periodic sync every 5 hours with 20 minutes of flex.
         *     SyncRequest.Builder builder =
         *         (new SyncRequest.Builder()).syncPeriodic(5 * HOUR_IN_SECS, 20 * MIN_IN_SECS);
         *
         *     Schedule a periodic sync every hour at any point in time during that hour.
         *     SyncRequest.Builder builder =
         *         (new SyncRequest.Builder()).syncPeriodic(1 * HOUR_IN_SECS, 1 * HOUR_IN_SECS);
         * </pre>
         *
         * N.B.: Periodic syncs are not allowed to have any of
         * {@link ContentResolver#SYNC_EXTRAS_DO_NOT_RETRY},
         * {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF},
         * {@link ContentResolver#SYNC_EXTRAS_IGNORE_SETTINGS},
         * {@link ContentResolver#SYNC_EXTRAS_INITIALIZE},
         * {@link ContentResolver#SYNC_EXTRAS_FORCE},
         * {@link ContentResolver#SYNC_EXTRAS_EXPEDITED},
         * {@link ContentResolver#SYNC_EXTRAS_MANUAL}
         * set to true. If any are supplied then an <code>IllegalArgumentException</code> will
         * be thrown.
         *
         * @param pollFrequency the amount of time in seconds that you wish
         *            to elapse between periodic syncs. A minimum period of 1 hour is enforced.
         * @param beforeSeconds the amount of flex time in seconds before
         *            {@code pollFrequency} that you permit for the sync to take
         *            place. Must be less than {@code pollFrequency} and greater than
         *            MAX(5% of {@code pollFrequency}, 5 minutes)
         */
        public Builder syncPeriodic(long pollFrequency, long beforeSeconds) {
            if (mSyncType != SYNC_TYPE_UNKNOWN) {
                throw new IllegalArgumentException("Sync type has already been defined.");
            }
            mSyncType = SYNC_TYPE_PERIODIC;
            setupInterval(pollFrequency, beforeSeconds);
            return this;
        }

        private void setupInterval(long at, long before) {
            if (before > at) {
                throw new IllegalArgumentException("Specified run time for the sync must be" +
                    " after the specified flex time.");
            }
            mSyncRunTimeSecs = at;
            mSyncFlexTimeSecs = before;
        }

        /**
         * Will throw an <code>IllegalArgumentException</code> if called and
         * {@link #setIgnoreSettings(boolean ignoreSettings)} has already been called.
         * @param disallow true to allow this transfer on metered networks. Default false.
         *
         */
        public Builder setDisallowMetered(boolean disallow) {
            if (mIgnoreSettings && disallow) {
                throw new IllegalArgumentException("setDisallowMetered(true) after having"
                        + " specified that settings are ignored.");
            }
            mDisallowMetered = disallow;
            return this;
        }

        /**
         * Specify whether the sync requires the phone to be plugged in.
         * @param requiresCharging true if sync requires the phone to be plugged in. Default false.
         */
        public Builder setRequiresCharging(boolean requiresCharging) {
            mRequiresCharging = requiresCharging;
            return this;
        }

        /**
         * Specify an authority and account for this transfer.
         *
         * @param authority A String identifying the content provider to be synced.
         * @param account Account to sync. Can be null unless this is a periodic
         *            sync, for which verification by the ContentResolver will
         *            fail. If a sync is performed without an account, the
         */
        public Builder setSyncAdapter(Account account, String authority) {
            if (mSyncTarget != SYNC_TARGET_UNKNOWN) {
                throw new IllegalArgumentException("Sync target has already been defined.");
            }
            if (authority != null && authority.length() == 0) {
                throw new IllegalArgumentException("Authority must be non-empty");
            }
            mSyncTarget = SYNC_TARGET_ADAPTER;
            mAccount = account;
            mAuthority = authority;
            return this;
        }

        /**
         * Developer-provided extras handed back when sync actually occurs. This bundle is copied
         * into the SyncRequest returned by {@link #build()}.
         *
         * Example:
         * <pre>
         *   String[] syncItems = {"dog", "cat", "frog", "child"};
         *   SyncRequest.Builder builder =
         *     new SyncRequest.Builder()
         *       .setSyncAdapter(dummyAccount, dummyProvider)
         *       .syncOnce();
         *
         *   for (String syncData : syncItems) {
         *     Bundle extras = new Bundle();
         *     extras.setString("data", syncData);
         *     builder.setExtras(extras);
         *     ContentResolver.sync(builder.build()); // Each sync() request creates a unique sync.
         *   }
         * </pre>
         * Only values of the following types may be used in the extras bundle:
         * <ul>
         * <li>Integer</li>
         * <li>Long</li>
         * <li>Boolean</li>
         * <li>Float</li>
         * <li>Double</li>
         * <li>String</li>
         * <li>Account</li>
         * <li>null</li>
         * </ul>
         * If any data is present in the bundle not of this type, build() will
         * throw a runtime exception.
         *
         * @param bundle extras bundle to set.
         */
        public Builder setExtras(Bundle bundle) {
            mCustomExtras = bundle;
            return this;
        }

        /**
         * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_DO_NOT_RETRY}.
         *
         * A one-off sync operation that fails will be retried with exponential back-off unless
         * this is set to false. Not valid for periodic sync and will throw an
         * <code>IllegalArgumentException</code> in build().
         *
         * @param noRetry true to not retry a failed sync. Default false.
         */
        public Builder setNoRetry(boolean noRetry) {
            mNoRetry = noRetry;
            return this;
        }

        /**
         * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_IGNORE_SETTINGS}.
         *
         * Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in
         * {@link #build()}.
         * <p>Throws <code>IllegalArgumentException</code> if called and
         * {@link #setDisallowMetered(boolean)} has been set.
         * 
         *
         * @param ignoreSettings true to ignore the sync automatically settings. Default false.
         */
        public Builder setIgnoreSettings(boolean ignoreSettings) {
            if (mDisallowMetered && ignoreSettings) {
                throw new IllegalArgumentException("setIgnoreSettings(true) after having specified"
                        + " sync settings with this builder.");
            }
            mIgnoreSettings = ignoreSettings;
            return this;
        }

        /**
         * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_IGNORE_BACKOFF}.
         *
         * Ignoring back-off will force the sync scheduling process to ignore any back-off that was
         * the result of a failed sync, as well as to invalidate any {@link SyncResult#delayUntil}
         * value that may have been set by the adapter. Successive failures will not honor this
         * flag. Not valid for periodic sync and will throw an <code>IllegalArgumentException</code>
         * in {@link #build()}.
         *
         * @param ignoreBackoff ignore back off settings. Default false.
         */
        public Builder setIgnoreBackoff(boolean ignoreBackoff) {
            mIgnoreBackoff = ignoreBackoff;
            return this;
        }

        /**
         * Convenience function for setting {@link ContentResolver#SYNC_EXTRAS_MANUAL}.
         *
         * Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in
         * {@link #build()}.
         *
         * @param isManual User-initiated sync or not. Default false.
         */
        public Builder setManual(boolean isManual) {
            mIsManual = isManual;
            return this;
        }

        /**
         * An expedited sync runs immediately and can preempt other non-expedited running syncs.
         *
         * Not valid for periodic sync and will throw an <code>IllegalArgumentException</code> in
         * {@link #build()}.
         *
         * @param expedited whether to run expedited. Default false.
         */
        public Builder setExpedited(boolean expedited) {
            mExpedited = expedited;
            return this;
        }

        /**
         * Performs validation over the request and throws the runtime exception
         * <code>IllegalArgumentException</code> if this validation fails.
         *
         * @return a SyncRequest with the information contained within this
         *         builder.
         */
        public SyncRequest build() {
            // Validate the extras bundle
            ContentResolver.validateSyncExtrasBundle(mCustomExtras);
            if (mCustomExtras == null) {
                mCustomExtras = new Bundle();
            }
            // Combine builder extra flags into the config bundle.
            mSyncConfigExtras = new Bundle();
            if (mIgnoreBackoff) {
                mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
            }
            if (mDisallowMetered) {
                mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, true);
            }
            if (mRequiresCharging) {
                mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_REQUIRE_CHARGING, true);
            }
            if (mIgnoreSettings) {
                mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
            }
            if (mNoRetry) {
                mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, true);
            }
            if (mExpedited) {
                mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
            }
            if (mIsManual) {
                mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
                mSyncConfigExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
            }
            if (mSyncType == SYNC_TYPE_PERIODIC) {
                // If this is a periodic sync ensure than invalid extras were not set.
                if (ContentResolver.invalidPeriodicExtras(mCustomExtras) ||
                        ContentResolver.invalidPeriodicExtras(mSyncConfigExtras)) {
                    throw new IllegalArgumentException("Illegal extras were set");
                }
            }
            // Ensure that a target for the sync has been set.
            if (mSyncTarget == SYNC_TARGET_UNKNOWN) {
                throw new IllegalArgumentException("Must specify an adapter with" +
                        " setSyncAdapter(Account, String");
            }
            return new SyncRequest(this);
        }
    }
}