summaryrefslogtreecommitdiffstats
path: root/core/java/android/content
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android/content')
-rw-r--r--core/java/android/content/AbstractThreadedSyncAdapter.java2
-rw-r--r--core/java/android/content/ContentResolver.java56
-rw-r--r--core/java/android/content/IAnonymousSyncAdapter.aidl45
-rw-r--r--core/java/android/content/IContentService.aidl2
-rw-r--r--core/java/android/content/PeriodicSync.java150
-rw-r--r--core/java/android/content/SyncRequest.aidl19
-rw-r--r--core/java/android/content/SyncRequest.java625
-rw-r--r--core/java/android/content/SyncResult.java7
-rw-r--r--core/java/android/content/SyncService.java169
9 files changed, 1051 insertions, 24 deletions
diff --git a/core/java/android/content/AbstractThreadedSyncAdapter.java b/core/java/android/content/AbstractThreadedSyncAdapter.java
index bafe67d..613450b 100644
--- a/core/java/android/content/AbstractThreadedSyncAdapter.java
+++ b/core/java/android/content/AbstractThreadedSyncAdapter.java
@@ -147,6 +147,7 @@ public abstract class AbstractThreadedSyncAdapter {
}
private class ISyncAdapterImpl extends ISyncAdapter.Stub {
+ @Override
public void startSync(ISyncContext syncContext, String authority, Account account,
Bundle extras) {
final SyncContext syncContextClient = new SyncContext(syncContext);
@@ -184,6 +185,7 @@ public abstract class AbstractThreadedSyncAdapter {
}
}
+ @Override
public void cancelSync(ISyncContext syncContext) {
// synchronize to make sure that mSyncThreads doesn't change between when we
// check it and when we use it
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index f090e07..243c91a 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -44,6 +44,7 @@ import android.os.UserHandle;
import android.text.TextUtils;
import android.util.EventLog;
import android.util.Log;
+import android.util.Pair;
import java.io.File;
import java.io.FileInputStream;
@@ -131,6 +132,19 @@ public abstract class ContentResolver {
*/
public static final String SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS = "discard_deletions";
+ /* Extensions to API. TODO: Not clear if we will keep these as public flags. */
+ /** {@hide} User-specified flag for expected upload size. */
+ public static final String SYNC_EXTRAS_EXPECTED_UPLOAD = "expected_upload";
+
+ /** {@hide} User-specified flag for expected download size. */
+ public static final String SYNC_EXTRAS_EXPECTED_DOWNLOAD = "expected_download";
+
+ /** {@hide} Priority of this sync with respect to other syncs scheduled for this application. */
+ public static final String SYNC_EXTRAS_PRIORITY = "sync_priority";
+
+ /** {@hide} Flag to allow sync to occur on metered network. */
+ public static final String SYNC_EXTRAS_ALLOW_METERED = "allow_metered";
+
/**
* Set by the SyncManager to request that the SyncAdapter initialize itself for
* the given account/authority pair. One required initialization step is to
@@ -1385,6 +1399,8 @@ public abstract class ContentResolver {
* <li>Float</li>
* <li>Double</li>
* <li>String</li>
+ * <li>Account</li>
+ * <li>null</li>
* </ul>
*
* @param uri the uri of the provider to sync or null to sync all providers.
@@ -1416,6 +1432,8 @@ public abstract class ContentResolver {
* <li>Float</li>
* <li>Double</li>
* <li>String</li>
+ * <li>Account</li>
+ * <li>null</li>
* </ul>
*
* @param account which account should be synced
@@ -1423,10 +1441,24 @@ public abstract class ContentResolver {
* @param extras any extras to pass to the SyncAdapter.
*/
public static void requestSync(Account account, String authority, Bundle extras) {
- validateSyncExtrasBundle(extras);
+ SyncRequest request =
+ new SyncRequest.Builder()
+ .setSyncAdapter(account, authority)
+ .setExtras(extras)
+ .syncOnce(0, 0) // Immediate sync.
+ .build();
+ requestSync(request);
+ }
+
+ /**
+ * Register a sync with the SyncManager. These requests are built using the
+ * {@link SyncRequest.Builder}.
+ */
+ public static void requestSync(SyncRequest request) {
try {
- getContentService().requestSync(account, authority, extras);
- } catch (RemoteException e) {
+ getContentService().sync(request);
+ } catch(RemoteException e) {
+ // Shouldn't happen.
}
}
@@ -1586,7 +1618,7 @@ public abstract class ContentResolver {
throw new IllegalArgumentException("illegal extras were set");
}
try {
- getContentService().addPeriodicSync(account, authority, extras, pollFrequency);
+ getContentService().addPeriodicSync(account, authority, extras, pollFrequency);
} catch (RemoteException e) {
// exception ignored; if this is thrown then it means the runtime is in the midst of
// being restarted
@@ -1619,6 +1651,22 @@ public abstract class ContentResolver {
}
/**
+ * Remove the specified sync. This will remove any syncs that have been scheduled to run, but
+ * will not cancel any running syncs.
+ * <p>This method requires the caller to hold the permission</p>
+ * If the request is for a periodic sync this will cancel future occurrences of the sync.
+ *
+ * It is possible to cancel a sync using a SyncRequest object that is different from the object
+ * with which you requested the sync. Do so by building a SyncRequest with exactly the same
+ * service/adapter, frequency, <b>and</b> extras bundle.
+ *
+ * @param request SyncRequest object containing information about sync to cancel.
+ */
+ public static void cancelSync(SyncRequest request) {
+ // TODO: Finish this implementation.
+ }
+
+ /**
* Get the list of information about the periodic syncs for the given account and authority.
* <p>This method requires the caller to hold the permission
* {@link android.Manifest.permission#READ_SYNC_SETTINGS}.
diff --git a/core/java/android/content/IAnonymousSyncAdapter.aidl b/core/java/android/content/IAnonymousSyncAdapter.aidl
new file mode 100644
index 0000000..a80cea3
--- /dev/null
+++ b/core/java/android/content/IAnonymousSyncAdapter.aidl
@@ -0,0 +1,45 @@
+/*
+ * 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.os.Bundle;
+import android.content.ISyncContext;
+
+/**
+ * Interface to define an anonymous service that is extended by developers
+ * in order to perform anonymous syncs (syncs without an Account or Content
+ * Provider specified). See {@link android.content.AbstractThreadedSyncAdapter}.
+ * {@hide}
+ */
+oneway interface IAnonymousSyncAdapter {
+
+ /**
+ * Initiate a sync. SyncAdapter-specific parameters may be specified in
+ * extras, which is guaranteed to not be null.
+ *
+ * @param syncContext the ISyncContext used to indicate the progress of the sync. When
+ * the sync is finished (successfully or not) ISyncContext.onFinished() must be called.
+ * @param extras SyncAdapter-specific parameters.
+ *
+ */
+ void startSync(ISyncContext syncContext, in Bundle extras);
+
+ /**
+ * Cancel the currently ongoing sync.
+ */
+ void cancelSync(ISyncContext syncContext);
+
+}
diff --git a/core/java/android/content/IContentService.aidl b/core/java/android/content/IContentService.aidl
index f956bcf..9ad5a19 100644
--- a/core/java/android/content/IContentService.aidl
+++ b/core/java/android/content/IContentService.aidl
@@ -20,6 +20,7 @@ import android.accounts.Account;
import android.content.SyncInfo;
import android.content.ISyncStatusObserver;
import android.content.SyncAdapterType;
+import android.content.SyncRequest;
import android.content.SyncStatusInfo;
import android.content.PeriodicSync;
import android.net.Uri;
@@ -54,6 +55,7 @@ interface IContentService {
int userHandle);
void requestSync(in Account account, String authority, in Bundle extras);
+ void sync(in SyncRequest request);
void cancelSync(in Account account, String authority);
/**
diff --git a/core/java/android/content/PeriodicSync.java b/core/java/android/content/PeriodicSync.java
index 513a556..6aca151 100644
--- a/core/java/android/content/PeriodicSync.java
+++ b/core/java/android/content/PeriodicSync.java
@@ -22,67 +22,170 @@ import android.os.Parcel;
import android.accounts.Account;
/**
- * Value type that contains information about a periodic sync. Is parcelable, making it suitable
- * for passing in an IPC.
+ * Value type that contains information about a periodic sync.
*/
public class PeriodicSync implements Parcelable {
- /** The account to be synced */
+ /** The account to be synced. Can be null. */
public final Account account;
- /** The authority of the sync */
+ /** The authority of the sync. Can be null. */
public final String authority;
+ /** The service for syncing, if this is an anonymous sync. Can be null.*/
+ public final ComponentName service;
/** Any extras that parameters that are to be passed to the sync adapter. */
public final Bundle extras;
- /** How frequently the sync should be scheduled, in seconds. */
+ /** How frequently the sync should be scheduled, in seconds. Kept around for API purposes. */
public final long period;
+ /** Whether this periodic sync uses a service. */
+ public final boolean isService;
+ /**
+ * How much flexibility can be taken in scheduling the sync, in seconds.
+ * {@hide}
+ */
+ public final long flexTime;
- /** Creates a new PeriodicSync, copying the Bundle */
- public PeriodicSync(Account account, String authority, Bundle extras, long period) {
+ /**
+ * Creates a new PeriodicSync, copying the Bundle. SM no longer uses this ctor - kept around
+ * becuse it is part of the API.
+ * Note - even calls to the old API will not use this ctor, as
+ * they are given a default flex time.
+ */
+ public PeriodicSync(Account account, String authority, Bundle extras, long periodInSeconds) {
this.account = account;
this.authority = authority;
+ this.service = null;
+ this.isService = false;
+ if (extras == null) {
+ this.extras = new Bundle();
+ } else {
+ this.extras = new Bundle(extras);
+ }
+ this.period = periodInSeconds;
+ // Old API uses default flex time. No-one should be using this ctor anyway.
+ this.flexTime = 0L;
+ }
+
+ // TODO: Add copy ctor from SyncRequest?
+
+ /**
+ * Create a copy of a periodic sync.
+ * {@hide}
+ */
+ public PeriodicSync(PeriodicSync other) {
+ this.account = other.account;
+ this.authority = other.authority;
+ this.service = other.service;
+ this.isService = other.isService;
+ this.extras = new Bundle(other.extras);
+ this.period = other.period;
+ this.flexTime = other.flexTime;
+ }
+
+ /**
+ * A PeriodicSync for a sync with a specified provider.
+ * {@hide}
+ */
+ public PeriodicSync(Account account, String authority, Bundle extras,
+ long period, long flexTime) {
+ this.account = account;
+ this.authority = authority;
+ this.service = null;
+ this.isService = false;
+ this.extras = new Bundle(extras);
+ this.period = period;
+ this.flexTime = flexTime;
+ }
+
+ /**
+ * A PeriodicSync for a sync with a specified SyncService.
+ * {@hide}
+ */
+ public PeriodicSync(ComponentName service, Bundle extras,
+ long period,
+ long flexTime) {
+ this.account = null;
+ this.authority = null;
+ this.service = service;
+ this.isService = true;
this.extras = new Bundle(extras);
this.period = period;
+ this.flexTime = flexTime;
}
+ private PeriodicSync(Parcel in) {
+ this.isService = (in.readInt() != 0);
+ if (this.isService) {
+ this.service = in.readParcelable(null);
+ this.account = null;
+ this.authority = null;
+ } else {
+ this.account = in.readParcelable(null);
+ this.authority = in.readString();
+ this.service = null;
+ }
+ this.extras = in.readBundle();
+ this.period = in.readLong();
+ this.flexTime = in.readLong();
+ }
+
+ @Override
public int describeContents() {
return 0;
}
+ @Override
public void writeToParcel(Parcel dest, int flags) {
- account.writeToParcel(dest, flags);
- dest.writeString(authority);
+ dest.writeInt(isService ? 1 : 0);
+ if (account == null && authority == null) {
+ dest.writeParcelable(service, flags);
+ } else {
+ dest.writeParcelable(account, flags);
+ dest.writeString(authority);
+ }
dest.writeBundle(extras);
dest.writeLong(period);
+ dest.writeLong(flexTime);
}
public static final Creator<PeriodicSync> CREATOR = new Creator<PeriodicSync>() {
+ @Override
public PeriodicSync createFromParcel(Parcel source) {
- return new PeriodicSync(Account.CREATOR.createFromParcel(source),
- source.readString(), source.readBundle(), source.readLong());
+ return new PeriodicSync(source);
}
+ @Override
public PeriodicSync[] newArray(int size) {
return new PeriodicSync[size];
}
};
+ @Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
-
if (!(o instanceof PeriodicSync)) {
return false;
}
-
final PeriodicSync other = (PeriodicSync) o;
-
- return account.equals(other.account)
- && authority.equals(other.authority)
- && period == other.period
- && syncExtrasEquals(extras, other.extras);
+ if (this.isService != other.isService) {
+ return false;
+ }
+ boolean equal = false;
+ if (this.isService) {
+ equal = service.equals(other.service);
+ } else {
+ equal = account.equals(other.account)
+ && authority.equals(other.authority);
+ }
+ return equal
+ && period == other.period
+ && syncExtrasEquals(extras, other.extras);
}
- /** {@hide} */
+ /**
+ * Periodic sync extra comparison function.
+ * {@hide}
+ */
public static boolean syncExtrasEquals(Bundle b1, Bundle b2) {
if (b1.size() != b2.size()) {
return false;
@@ -100,4 +203,13 @@ public class PeriodicSync implements Parcelable {
}
return true;
}
+
+ @Override
+ public String toString() {
+ return "account: " + account +
+ ", authority: " + authority +
+ ", service: " + service +
+ ". period: " + period + "s " +
+ ", flex: " + flexTime;
+ }
}
diff --git a/core/java/android/content/SyncRequest.aidl b/core/java/android/content/SyncRequest.aidl
new file mode 100644
index 0000000..8321fac
--- /dev/null
+++ b/core/java/android/content/SyncRequest.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable SyncRequest;
diff --git a/core/java/android/content/SyncRequest.java b/core/java/android/content/SyncRequest.java
new file mode 100644
index 0000000..336371e
--- /dev/null
+++ b/core/java/android/content/SyncRequest.java
@@ -0,0 +1,625 @@
+/*
+ * 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;
+import android.util.Pair;
+
+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;
+ /** {@link SyncService} identifier. */
+ private final ComponentName mComponentInfo;
+ /** Bundle containing user info as well as sync settings. */
+ private final Bundle mExtras;
+ /** Allow this sync request on metered networks. */
+ private final boolean mAllowMetered;
+ /**
+ * Anticipated upload size in bytes.
+ * TODO: Not yet used - we put this information into the bundle for simplicity.
+ */
+ private final long mTxBytes;
+ /**
+ * Anticipated download size in bytes.
+ * TODO: Not yet used - we put this information into the bundle.
+ */
+ private final long mRxBytes;
+ /**
+ * 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;
+ }
+
+ public boolean isExpedited() {
+ return mIsExpedited;
+ }
+
+ /**
+ * {@hide}
+ * @return true if this sync uses an account/authority pair, or false if
+ * this is an anonymous sync bound to an @link AnonymousSyncService.
+ */
+ public boolean hasAuthority() {
+ return mIsAuthority;
+ }
+
+ /**
+ * {@hide}
+ * Throws a runtime IllegalArgumentException if this function is called for an
+ * anonymous sync.
+ *
+ * @return (Account, Provider) for this SyncRequest.
+ */
+ public Pair<Account, String> getProviderInfo() {
+ if (!hasAuthority()) {
+ throw new IllegalArgumentException("Cannot getProviderInfo() for an anonymous sync.");
+ }
+ return Pair.create(mAccountToSync, mAuthority);
+ }
+
+ /**
+ * {@hide}
+ * Throws a runtime IllegalArgumentException if this function is called for a
+ * SyncRequest that is bound to an account/provider.
+ *
+ * @return ComponentName for the service that this sync will bind to.
+ */
+ public ComponentName getService() {
+ if (hasAuthority()) {
+ throw new IllegalArgumentException(
+ "Cannot getAnonymousService() for a sync that has specified a provider.");
+ }
+ return mComponentInfo;
+ }
+
+ /**
+ * {@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((mAllowMetered ? 1 : 0));
+ parcel.writeLong(mTxBytes);
+ parcel.writeLong(mRxBytes);
+ parcel.writeInt((mIsAuthority ? 1 : 0));
+ parcel.writeInt((mIsExpedited? 1 : 0));
+ if (mIsAuthority) {
+ parcel.writeParcelable(mAccountToSync, flags);
+ parcel.writeString(mAuthority);
+ } else {
+ parcel.writeParcelable(mComponentInfo, flags);
+ }
+ }
+
+ private SyncRequest(Parcel in) {
+ mExtras = in.readBundle();
+ mSyncFlexTimeSecs = in.readLong();
+ mSyncRunTimeSecs = in.readLong();
+ mIsPeriodic = (in.readInt() != 0);
+ mAllowMetered = (in.readInt() != 0);
+ mTxBytes = in.readLong();
+ mRxBytes = in.readLong();
+ mIsAuthority = (in.readInt() != 0);
+ mIsExpedited = (in.readInt() != 0);
+ if (mIsAuthority) {
+ mComponentInfo = null;
+ mAccountToSync = in.readParcelable(null);
+ mAuthority = in.readString();
+ } else {
+ mComponentInfo = in.readParcelable(null);
+ mAccountToSync = null;
+ mAuthority = null;
+ }
+ }
+
+ /** {@hide} Protected ctor to instantiate anonymous SyncRequest. */
+ protected SyncRequest(SyncRequest.Builder b) {
+ mSyncFlexTimeSecs = b.mSyncFlexTimeSecs;
+ mSyncRunTimeSecs = b.mSyncRunTimeSecs;
+ mAccountToSync = b.mAccount;
+ mAuthority = b.mAuthority;
+ mComponentInfo = b.mComponentName;
+ mIsPeriodic = (b.mSyncType == Builder.SYNC_TYPE_PERIODIC);
+ mIsAuthority = (b.mSyncTarget == Builder.SYNC_TARGET_ADAPTER);
+ mIsExpedited = b.mExpedited;
+ mExtras = new Bundle(b.mCustomExtras);
+ mAllowMetered = b.mAllowMetered;
+ mTxBytes = b.mTxBytes;
+ mRxBytes = b.mRxBytes;
+ }
+
+ /**
+ * 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 an anonymous sync. */
+ private static final int SYNC_TARGET_SERVICE = 1;
+ /** 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;
+ /** Expected upload transfer in bytes. */
+ private long mTxBytes = -1L;
+ /** Expected download transfer in bytes. */
+ private long mRxBytes = -1L;
+ /** Whether or not this sync can occur on metered networks. Default false. */
+ private boolean mAllowMetered;
+ /** Priority of this sync relative to others from calling app [-2, 2]. Default 0. */
+ private int mPriority = 0;
+ /**
+ * 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 or to a sync service. */
+ 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 android.content.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 @link android.content.AnonymousSyncService component that
+ * contains the sync logic if this is a provider-less sync, otherwise
+ * null.
+ */
+ private ComponentName mComponentName;
+ /**
+ * The Account object that together with an Authority name define the SyncAdapter (if
+ * this sync is bound to a provider), otherwise null. This gets resolved
+ * against a {@link com.android.server.content.SyncStorageEngine}.
+ */
+ private Account mAccount;
+ /**
+ * The Authority name that together with an Account define the SyncAdapter (if
+ * this sync is bound to a provider), otherwise null. This gets resolved
+ * against a {@link com.android.server.content.SyncStorageEngine}.
+ */
+ private String mAuthority;
+
+ public Builder() {
+ }
+
+ /**
+ * Developer can define timing constraints for this one-shot request.
+ * These values are elapsed real-time.
+ *
+ * @param whenSeconds The time in seconds at which you want this
+ * sync to occur.
+ * @param beforeSeconds The amount of time in advance of whenSeconds that this
+ * sync may be permitted to occur. This is rounded up to a minimum of 5
+ * seconds, for any sync for which whenSeconds > 5.
+ *
+ * Example
+ * <pre>
+ * Perform an immediate sync.
+ * SyncRequest.Builder builder = (new SyncRequest.Builder()).syncOnce(0, 0);
+ * That is, a sync 0 seconds from now with 0 seconds of flex.
+ *
+ * Perform a sync in exactly 5 minutes.
+ * SyncRequest.Builder builder =
+ * new SyncRequest.Builder().syncOnce(5 * MIN_IN_SECS, 0);
+ *
+ * Perform a sync in 5 minutes, with one minute of leeway (between 4 and 5 minutes from
+ * now).
+ * SyncRequest.Builder builder =
+ * new SyncRequest.Builder().syncOnce(5 * MIN_IN_SECS, 1 * MIN_IN_SECS);
+ * </pre>
+ */
+ public Builder syncOnce(long whenSeconds, long beforeSeconds) {
+ if (mSyncType != SYNC_TYPE_UNKNOWN) {
+ throw new IllegalArgumentException("Sync type has already been defined.");
+ }
+ mSyncType = SYNC_TYPE_ONCE;
+ setupInterval(whenSeconds, beforeSeconds);
+ return this;
+ }
+
+ /**
+ * Build a periodic sync. Either this or syncOnce() <b>must</b> be called for this builder.
+ * Syncs are identified by target {@link SyncService}/{@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 {@link IllegalArgumentException} will be
+ * thrown.
+ *
+ * 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 #SYNC_EXTRAS_DO_NOT_RETRY},
+ * {@link #SYNC_EXTRAS_IGNORE_BACKOFF},
+ * {@link #SYNC_EXTRAS_IGNORE_SETTINGS},
+ * {@link #SYNC_EXTRAS_INITIALIZE},
+ * {@link #SYNC_EXTRAS_FORCE},
+ * {@link #SYNC_EXTRAS_EXPEDITED},
+ * {@link #SYNC_EXTRAS_MANUAL}
+ * set to true. If any are supplied then an {@link IllegalArgumentException} will
+ * be thrown.
+ *
+ * @param pollFrequency the amount of time in seconds that you wish
+ * to elapse between periodic syncs.
+ * @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}.
+ */
+ 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;
+ }
+
+ /** {@hide} */
+ 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;
+ }
+
+ /**
+ * Developer can provide insight into their payload size; optional. -1 specifies
+ * unknown, so that you are not restricted to defining both fields.
+ *
+ * @param rxBytes Bytes expected to be downloaded.
+ * @param txBytes Bytes expected to be uploaded.
+ */
+ public Builder setTransferSize(long rxBytes, long txBytes) {
+ mRxBytes = rxBytes;
+ mTxBytes = txBytes;
+ return this;
+ }
+
+ /**
+ * @param allow false to allow this transfer on metered networks.
+ * Default true.
+ */
+ public Builder setAllowMetered(boolean allow) {
+ mAllowMetered = true;
+ return this;
+ }
+
+ /**
+ * Give ourselves a concrete way of binding. Use an explicit
+ * authority+account SyncAdapter for this transfer, otherwise we bind
+ * anonymously to given componentname.
+ *
+ * @param authority
+ * @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.");
+ }
+ mSyncTarget = SYNC_TARGET_ADAPTER;
+ mAccount = account;
+ mAuthority = authority;
+ mComponentName = null;
+ return this;
+ }
+
+ /**
+ * Set Service component name for anonymous sync. This is not validated
+ * until sync time so providing an incorrect component name here will
+ * not fail.
+ *
+ * @param cname ComponentName to identify your Anonymous service
+ */
+ public Builder setSyncAdapter(ComponentName cname) {
+ if (mSyncTarget != SYNC_TARGET_UNKNOWN) {
+ throw new IllegalArgumentException("Sync target has already been defined.");
+ }
+ mSyncTarget = SYNC_TARGET_SERVICE;
+ mComponentName = cname;
+ mAccount = null;
+ mAuthority = null;
+ return this;
+ }
+
+ /**
+ * Developer-provided extras handed back when sync actually occurs. This bundle is copied
+ * into the SyncRequest returned by build().
+ *
+ * Example:
+ * <pre>
+ * String[] syncItems = {"dog", "cat", "frog", "child"};
+ * SyncRequest.Builder builder =
+ * new SyncRequest.Builder()
+ * .setSyncAdapter(dummyAccount, dummyProvider)
+ * .syncOnce(5 * MINUTES_IN_SECS);
+ *
+ * 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
+ */
+ 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 at a later date unless this is
+ * set to false. Default is true. Not valid for periodic sync and will throw an
+ * IllegalArgumentException in Builder.build().
+ *
+ * @param retry false to not retry a failed sync. Default true.
+ */
+ public Builder setNoRetry(boolean retry) {
+ mNoRetry = retry;
+ return this;
+ }
+
+ /**
+ * {@link ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS}. Not valid for
+ * periodic sync and will throw an IllegalArgumentException in
+ * Builder.build(). Default false.
+ */
+ public Builder setIgnoreSettings(boolean ignoreSettings) {
+ mIgnoreSettings = ignoreSettings;
+ return this;
+ }
+
+ /**
+ * Convenience function for setting {@link ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF}.
+ *
+ * @param ignoreBackoff
+ */
+ public Builder setIgnoreBackoff(boolean ignoreBackoff) {
+ mIgnoreBackoff = ignoreBackoff;
+ return this;
+ }
+
+ /**
+ * {@link ContentResolver.SYNC_EXTRAS_MANUAL}. Default false.
+ */
+ public Builder setManual(boolean isManual) {
+ mIsManual = isManual;
+ return this;
+ }
+
+ /**
+ * {@link ContentResolver.SYNC_EXTRAS_} Default false.
+ */
+ public Builder setExpedited(boolean expedited) {
+ mExpedited = expedited;
+ return this;
+ }
+
+ /**
+ * Priority of this request among all requests from the calling app.
+ * Range of [-2,2] similar to {@link android.app.Notification.priority}.
+ */
+ public Builder setPriority(int priority) {
+ if (priority < -2 || priority > 2) {
+ throw new IllegalArgumentException("Priority must be within range [-2, 2]");
+ }
+ mPriority = priority;
+ return this;
+ }
+
+ /**
+ * Performs validation over the request and throws the runtime exception
+ * IllegalArgumentException if this validation fails. TODO: Add
+ * validation of SyncRequest here. 1) Cannot specify both periodic &
+ * one-off (fails above). 2) Cannot specify both service and
+ * account/provider (fails above).
+ *
+ * @return a SyncRequest with the information contained within this
+ * builder.
+ */
+ public SyncRequest build() {
+ // Validate the extras bundle
+ try {
+ ContentResolver.validateSyncExtrasBundle(mCustomExtras);
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException(e.getMessage());
+ }
+ if (mCustomExtras == null) {
+ mCustomExtras = new Bundle();
+ }
+ // Combine the builder extra flags into the copy of the bundle.
+ if (mIgnoreBackoff) {
+ mCustomExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
+ }
+ if (mAllowMetered) {
+ mCustomExtras.putBoolean(ContentResolver.SYNC_EXTRAS_ALLOW_METERED, true);
+ }
+ if (mIgnoreSettings) {
+ mCustomExtras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
+ }
+ if (mNoRetry) {
+ mCustomExtras.putBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, true);
+ }
+ if (mExpedited) {
+ mCustomExtras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
+ }
+ if (mIsManual) {
+ mCustomExtras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
+ }
+ // Upload/download expectations.
+ mCustomExtras.putLong(ContentResolver.SYNC_EXTRAS_EXPECTED_UPLOAD, mTxBytes);
+ mCustomExtras.putLong(ContentResolver.SYNC_EXTRAS_EXPECTED_DOWNLOAD, mRxBytes);
+ // Priority.
+ mCustomExtras.putInt(ContentResolver.SYNC_EXTRAS_PRIORITY, mPriority);
+ if (mSyncType == SYNC_TYPE_PERIODIC) {
+ // If this is a periodic sync ensure than invalid extras were
+ // not set.
+ if (mCustomExtras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false)
+ || mCustomExtras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false)
+ || mCustomExtras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)
+ || mCustomExtras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false)
+ || mCustomExtras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)
+ || mCustomExtras.getBoolean(ContentResolver.SYNC_EXTRAS_FORCE, false)
+ || mCustomExtras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false)) {
+ throw new IllegalArgumentException("Illegal extras were set");
+ }
+ } else if (mSyncType == SYNC_TYPE_UNKNOWN) {
+ throw new IllegalArgumentException("Must call either syncOnce() or syncPeriodic()");
+ }
+ // Ensure that a target for the sync has been set.
+ if (mSyncTarget == SYNC_TARGET_UNKNOWN) {
+ throw new IllegalArgumentException("Must specify an adapter with one of"
+ + "setSyncAdapter(ComponentName) or setSyncAdapter(Account, String");
+ }
+ return new SyncRequest(this);
+ }
+ }
+}
diff --git a/core/java/android/content/SyncResult.java b/core/java/android/content/SyncResult.java
index 6cb0d02..4f86af9 100644
--- a/core/java/android/content/SyncResult.java
+++ b/core/java/android/content/SyncResult.java
@@ -181,7 +181,7 @@ public final class SyncResult implements Parcelable {
* <li> {@link SyncStats#numIoExceptions} > 0
* <li> {@link #syncAlreadyInProgress}
* </ul>
- * @return true if a hard error is indicated
+ * @return true if a soft error is indicated
*/
public boolean hasSoftError() {
return syncAlreadyInProgress || stats.numIoExceptions > 0;
@@ -195,6 +195,11 @@ public final class SyncResult implements Parcelable {
return hasSoftError() || hasHardError();
}
+ /**
+ * Convenience method for determining if the Sync should be rescheduled after failing for some
+ * reason.
+ * @return true if the SyncManager should reschedule this sync.
+ */
public boolean madeSomeProgress() {
return ((stats.numDeletes > 0) && !tooManyDeletions)
|| stats.numInserts > 0
diff --git a/core/java/android/content/SyncService.java b/core/java/android/content/SyncService.java
new file mode 100644
index 0000000..100fd40
--- /dev/null
+++ b/core/java/android/content/SyncService.java
@@ -0,0 +1,169 @@
+/*
+ * 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.app.Service;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.Trace;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.HashMap;
+
+/**
+ * Simplified @link android.content.AbstractThreadedSyncAdapter. Folds that
+ * behaviour into a service to which the system can bind when requesting an
+ * anonymous (providerless/accountless) sync.
+ * <p>
+ * In order to perform an anonymous sync operation you must extend this service,
+ * implementing the abstract methods. This service must then be declared in the
+ * application's manifest as usual. You can use this service for other work, however you
+ * <b> must not </b> override the onBind() method unless you know what you're doing,
+ * which limits the usefulness of this service for other work.
+ *
+ * <pre>
+ * &lt;service ndroid:name=".MyAnonymousSyncService" android:permission="android.permission.SYNC" /&gt;
+ * </pre>
+ * Like @link android.content.AbstractThreadedSyncAdapter this service supports
+ * multiple syncs at the same time. Each incoming startSync() with a unique tag
+ * will spawn a thread to do the work of that sync. If startSync() is called
+ * with a tag that already exists, a SyncResult.ALREADY_IN_PROGRESS is returned.
+ * Remember that your service will spawn multiple threads if you schedule multiple syncs
+ * at once, so if you mutate local objects you must ensure synchronization.
+ */
+public abstract class SyncService extends Service {
+
+ /** SyncAdapter Instantiation that any anonymous syncs call. */
+ private final AnonymousSyncAdapterImpl mSyncAdapter = new AnonymousSyncAdapterImpl();
+
+ /** Keep track of on-going syncs, keyed by tag. */
+ @GuardedBy("mLock")
+ private final HashMap<Bundle, AnonymousSyncThread>
+ mSyncThreads = new HashMap<Bundle, AnonymousSyncThread>();
+ /** Lock object for accessing the SyncThreads HashMap. */
+ private final Object mSyncThreadLock = new Object();
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mSyncAdapter.asBinder();
+ }
+
+ /** {@hide} */
+ private class AnonymousSyncAdapterImpl extends IAnonymousSyncAdapter.Stub {
+
+ @Override
+ public void startSync(ISyncContext syncContext, Bundle extras) {
+ // Wrap the provided Sync Context because it may go away by the time
+ // we call it.
+ final SyncContext syncContextClient = new SyncContext(syncContext);
+ boolean alreadyInProgress = false;
+ synchronized (mSyncThreadLock) {
+ if (mSyncThreads.containsKey(extras)) {
+ // Don't want to call back to SyncManager while still
+ // holding lock.
+ alreadyInProgress = true;
+ } else {
+ AnonymousSyncThread syncThread = new AnonymousSyncThread(
+ syncContextClient, extras);
+ mSyncThreads.put(extras, syncThread);
+ syncThread.start();
+ }
+ }
+ if (alreadyInProgress) {
+ syncContextClient.onFinished(SyncResult.ALREADY_IN_PROGRESS);
+ }
+ }
+
+ /**
+ * Used by the SM to cancel a specific sync using the {@link
+ * com.android.server.content.SyncManager.ActiveSyncContext} as a handle.
+ */
+ @Override
+ public void cancelSync(ISyncContext syncContext) {
+ AnonymousSyncThread runningSync = null;
+ synchronized (mSyncThreadLock) {
+ for (AnonymousSyncThread thread : mSyncThreads.values()) {
+ if (thread.mSyncContext.getSyncContextBinder() == syncContext.asBinder()) {
+ runningSync = thread;
+ break;
+ }
+ }
+ }
+ if (runningSync != null) {
+ runningSync.interrupt();
+ }
+ }
+ }
+
+ /**
+ * {@hide}
+ * Similar to {@link android.content.AbstractThreadedSyncAdapter.SyncThread}. However while
+ * the ATSA considers an already in-progress sync to be if the account provided is currently
+ * syncing, this anonymous sync has no notion of account and therefore considers a sync unique
+ * if the provided bundle is different.
+ */
+ private class AnonymousSyncThread extends Thread {
+ private final SyncContext mSyncContext;
+ private final Bundle mExtras;
+
+ public AnonymousSyncThread(SyncContext syncContext, Bundle extras) {
+ mSyncContext = syncContext;
+ mExtras = extras;
+ }
+
+ @Override
+ public void run() {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+
+ Trace.traceBegin(Trace.TRACE_TAG_SYNC_MANAGER, getApplication().getPackageName());
+
+ SyncResult syncResult = new SyncResult();
+ try {
+ if (isCancelled()) {
+ return;
+ }
+ // Run the sync based off of the provided code.
+ SyncService.this.onPerformSync(mExtras, syncResult);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_SYNC_MANAGER);
+ if (!isCancelled()) {
+ mSyncContext.onFinished(syncResult);
+ }
+ // Synchronize so that the assignment will be seen by other
+ // threads
+ // that also synchronize accesses to mSyncThreads.
+ synchronized (mSyncThreadLock) {
+ mSyncThreads.remove(mExtras);
+ }
+ }
+ }
+
+ private boolean isCancelled() {
+ return Thread.currentThread().isInterrupted();
+ }
+ }
+
+ /**
+ * Initiate an anonymous sync using this service. SyncAdapter-specific
+ * parameters may be specified in extras, which is guaranteed to not be
+ * null.
+ */
+ public abstract void onPerformSync(Bundle extras, SyncResult syncResult);
+
+}