summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk2
-rw-r--r--api/current.txt35
-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
-rw-r--r--services/java/com/android/server/content/ContentService.java112
-rw-r--r--services/java/com/android/server/content/SyncManager.java399
-rw-r--r--services/java/com/android/server/content/SyncOperation.java185
-rw-r--r--services/java/com/android/server/content/SyncQueue.java41
-rw-r--r--services/java/com/android/server/content/SyncStorageEngine.java740
-rw-r--r--services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java44
-rw-r--r--services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java276
18 files changed, 2503 insertions, 406 deletions
diff --git a/Android.mk b/Android.mk
index 98bd88d..1fa8794 100644
--- a/Android.mk
+++ b/Android.mk
@@ -104,6 +104,7 @@ LOCAL_SRC_FILES += \
core/java/android/content/IIntentReceiver.aidl \
core/java/android/content/IIntentSender.aidl \
core/java/android/content/IOnPrimaryClipChangedListener.aidl \
+ core/java/android/content/IAnonymousSyncAdapter.aidl \
core/java/android/content/ISyncAdapter.aidl \
core/java/android/content/ISyncContext.aidl \
core/java/android/content/ISyncStatusObserver.aidl \
@@ -339,6 +340,7 @@ aidl_files := \
frameworks/base/core/java/android/content/Intent.aidl \
frameworks/base/core/java/android/content/IntentSender.aidl \
frameworks/base/core/java/android/content/PeriodicSync.aidl \
+ frameworks/base/core/java/android/content/SyncRequest.aidl \
frameworks/base/core/java/android/content/SyncStats.aidl \
frameworks/base/core/java/android/content/res/Configuration.aidl \
frameworks/base/core/java/android/database/CursorWindow.aidl \
diff --git a/api/current.txt b/api/current.txt
index ee0c395..d675ab0 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5572,6 +5572,7 @@ package android.content {
method public final android.os.Bundle call(android.net.Uri, java.lang.String, java.lang.String, android.os.Bundle);
method public deprecated void cancelSync(android.net.Uri);
method public static void cancelSync(android.accounts.Account, java.lang.String);
+ method public static void cancelSync(android.content.SyncRequest);
method public final int delete(android.net.Uri, java.lang.String, java.lang.String[]);
method public static deprecated android.content.SyncInfo getCurrentSync();
method public static java.util.List<android.content.SyncInfo> getCurrentSyncs();
@@ -5599,6 +5600,7 @@ package android.content {
method public static void removePeriodicSync(android.accounts.Account, java.lang.String, android.os.Bundle);
method public static void removeStatusChangeListener(java.lang.Object);
method public static void requestSync(android.accounts.Account, java.lang.String, android.os.Bundle);
+ method public static void requestSync(android.content.SyncRequest);
method public static void setIsSyncable(android.accounts.Account, java.lang.String, int);
method public static void setMasterSyncAutomatically(boolean);
method public static void setSyncAutomatically(android.accounts.Account, java.lang.String, boolean);
@@ -6534,7 +6536,9 @@ package android.content {
field public final android.accounts.Account account;
field public final java.lang.String authority;
field public final android.os.Bundle extras;
+ field public final boolean isService;
field public final long period;
+ field public final android.content.ComponentName service;
}
public class ReceiverCallNotAllowedException extends android.util.AndroidRuntimeException {
@@ -6653,6 +6657,31 @@ package android.content {
field public final long startTime;
}
+ public class SyncRequest implements android.os.Parcelable {
+ method public int describeContents();
+ method public boolean isExpedited();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator CREATOR;
+ }
+
+ public static class SyncRequest.Builder {
+ ctor public SyncRequest.Builder();
+ method public android.content.SyncRequest build();
+ method public android.content.SyncRequest.Builder setAllowMetered(boolean);
+ method public android.content.SyncRequest.Builder setExpedited(boolean);
+ method public android.content.SyncRequest.Builder setExtras(android.os.Bundle);
+ method public android.content.SyncRequest.Builder setIgnoreBackoff(boolean);
+ method public android.content.SyncRequest.Builder setIgnoreSettings(boolean);
+ method public android.content.SyncRequest.Builder setManual(boolean);
+ method public android.content.SyncRequest.Builder setNoRetry(boolean);
+ method public android.content.SyncRequest.Builder setPriority(int);
+ method public android.content.SyncRequest.Builder setSyncAdapter(android.accounts.Account, java.lang.String);
+ method public android.content.SyncRequest.Builder setSyncAdapter(android.content.ComponentName);
+ method public android.content.SyncRequest.Builder setTransferSize(long, long);
+ method public android.content.SyncRequest.Builder syncOnce(long, long);
+ method public android.content.SyncRequest.Builder syncPeriodic(long, long);
+ }
+
public final class SyncResult implements android.os.Parcelable {
ctor public SyncResult();
method public void clear();
@@ -6676,6 +6705,12 @@ package android.content {
field public boolean tooManyRetries;
}
+ public abstract class SyncService extends android.app.Service {
+ ctor public SyncService();
+ method public android.os.IBinder onBind(android.content.Intent);
+ method public abstract void onPerformSync(android.os.Bundle, android.content.SyncResult);
+ }
+
public class SyncStats implements android.os.Parcelable {
ctor public SyncStats();
ctor public SyncStats(android.os.Parcel);
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);
+
+}
diff --git a/services/java/com/android/server/content/ContentService.java b/services/java/com/android/server/content/ContentService.java
index 4a5c0d5..a56af08 100644
--- a/services/java/com/android/server/content/ContentService.java
+++ b/services/java/com/android/server/content/ContentService.java
@@ -19,6 +19,7 @@ package com.android.server.content;
import android.Manifest;
import android.accounts.Account;
import android.app.ActivityManager;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.IContentService;
@@ -26,6 +27,7 @@ import android.content.ISyncStatusObserver;
import android.content.PeriodicSync;
import android.content.SyncAdapterType;
import android.content.SyncInfo;
+import android.content.SyncRequest;
import android.content.SyncStatusInfo;
import android.database.IContentObserver;
import android.database.sqlite.SQLiteException;
@@ -39,6 +41,7 @@ import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.Log;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseIntArray;
@@ -312,6 +315,7 @@ public final class ContentService extends IContentService.Stub {
}
}
+ @Override
public void requestSync(Account account, String authority, Bundle extras) {
ContentResolver.validateSyncExtrasBundle(extras);
int userId = UserHandle.getCallingUserId();
@@ -323,7 +327,8 @@ public final class ContentService extends IContentService.Stub {
try {
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
- syncManager.scheduleSync(account, userId, uId, authority, extras, 0 /* no delay */,
+ syncManager.scheduleSync(account, userId, uId, authority, extras,
+ 0 /* no delay */, 0 /* no delay */,
false /* onlyThoseWithUnkownSyncableState */);
}
} finally {
@@ -332,11 +337,83 @@ public final class ContentService extends IContentService.Stub {
}
/**
+ * Request a sync with a generic {@link android.content.SyncRequest} object. This will be
+ * either:
+ * periodic OR one-off sync.
+ * and
+ * anonymous OR provider sync.
+ * Depending on the request, we enqueue to suit in the SyncManager.
+ * @param request
+ */
+ @Override
+ public void sync(SyncRequest request) {
+ Bundle extras = request.getBundle();
+ ContentResolver.validateSyncExtrasBundle(extras);
+
+ long flextime = request.getSyncFlexTime();
+ long runAtTime = request.getSyncRunTime();
+ int userId = UserHandle.getCallingUserId();
+ int uId = Binder.getCallingUid();
+
+ // This makes it so that future permission checks will be in the context of this
+ // process rather than the caller's process. We will restore this before returning.
+ long identityToken = clearCallingIdentity();
+ try {
+ SyncManager syncManager = getSyncManager();
+ if (syncManager != null) {
+ if (request.hasAuthority()) {
+ // Sync Adapter registered with the system - old API.
+ final Account account = request.getProviderInfo().first;
+ final String provider = request.getProviderInfo().second;
+ if (request.isPeriodic()) {
+ mContext.enforceCallingOrSelfPermission(
+ Manifest.permission.WRITE_SYNC_SETTINGS,
+ "no permission to write the sync settings");
+ if (runAtTime < 60) {
+ Slog.w(TAG, "Requested poll frequency of " + runAtTime
+ + " seconds being rounded up to 60 seconds.");
+ runAtTime = 60;
+ }
+ PeriodicSync syncToAdd =
+ new PeriodicSync(account, provider, extras, runAtTime, flextime);
+ getSyncManager().getSyncStorageEngine().addPeriodicSync(syncToAdd, userId);
+ } else {
+ long beforeRuntimeMillis = (flextime) * 1000;
+ long runtimeMillis = runAtTime * 1000;
+ syncManager.scheduleSync(
+ account, userId, uId, provider, extras,
+ beforeRuntimeMillis, runtimeMillis,
+ false /* onlyThoseWithUnknownSyncableState */);
+ }
+ } else {
+ // Anonymous sync - new API.
+ final ComponentName syncService = request.getService();
+ if (request.isPeriodic()) {
+ throw new RuntimeException("Periodic anonymous syncs not implemented yet.");
+ } else {
+ long beforeRuntimeMillis = (flextime) * 1000;
+ long runtimeMillis = runAtTime * 1000;
+ syncManager.scheduleSync(
+ syncService, userId, uId, extras,
+ beforeRuntimeMillis,
+ runtimeMillis,
+ false /* onlyThoseWithUnknownSyncableState */); // Empty function.
+ throw new RuntimeException("One-off anonymous syncs not implemented yet.");
+ }
+ }
+ }
+ } finally {
+ restoreCallingIdentity(identityToken);
+ }
+ }
+
+ /**
* Clear all scheduled sync operations that match the uri and cancel the active sync
* if they match the authority and account, if they are present.
* @param account filter the pending and active syncs to cancel using this account
* @param authority filter the pending and active syncs to cancel using this authority
*/
+ @Override
public void cancelSync(Account account, String authority) {
int userId = UserHandle.getCallingUserId();
@@ -358,6 +435,7 @@ public final class ContentService extends IContentService.Stub {
* Get information about the SyncAdapters that are known to the system.
* @return an array of SyncAdapters that have registered with the system
*/
+ @Override
public SyncAdapterType[] getSyncAdapterTypes() {
// This makes it so that future permission checks will be in the context of this
// process rather than the caller's process. We will restore this before returning.
@@ -371,6 +449,7 @@ public final class ContentService extends IContentService.Stub {
}
}
+ @Override
public boolean getSyncAutomatically(Account account, String providerName) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
"no permission to read the sync settings");
@@ -389,6 +468,7 @@ public final class ContentService extends IContentService.Stub {
return false;
}
+ @Override
public void setSyncAutomatically(Account account, String providerName, boolean sync) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
"no permission to write the sync settings");
@@ -406,6 +486,10 @@ public final class ContentService extends IContentService.Stub {
}
}
+ /**
+ * Old API. Schedule periodic sync with default flex time.
+ */
+ @Override
public void addPeriodicSync(Account account, String authority, Bundle extras,
long pollFrequency) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
@@ -420,13 +504,18 @@ public final class ContentService extends IContentService.Stub {
long identityToken = clearCallingIdentity();
try {
- getSyncManager().getSyncStorageEngine().addPeriodicSync(
- account, userId, authority, extras, pollFrequency);
+ // Add default flex time to this sync.
+ PeriodicSync syncToAdd =
+ new PeriodicSync(account, authority, extras,
+ pollFrequency,
+ SyncStorageEngine.calculateDefaultFlexTime(pollFrequency));
+ getSyncManager().getSyncStorageEngine().addPeriodicSync(syncToAdd, userId);
} finally {
restoreCallingIdentity(identityToken);
}
}
+ @Override
public void removePeriodicSync(Account account, String authority, Bundle extras) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
"no permission to write the sync settings");
@@ -434,13 +523,23 @@ public final class ContentService extends IContentService.Stub {
long identityToken = clearCallingIdentity();
try {
- getSyncManager().getSyncStorageEngine().removePeriodicSync(account, userId, authority,
- extras);
+ PeriodicSync syncToRemove = new PeriodicSync(account, authority, extras,
+ 0 /* Not read for removal */, 0 /* Not read for removal */);
+ getSyncManager().getSyncStorageEngine().removePeriodicSync(syncToRemove, userId);
} finally {
restoreCallingIdentity(identityToken);
}
}
+ /**
+ * TODO: Implement.
+ * @param request Sync to remove.
+ */
+ public void removeSync(SyncRequest request) {
+
+ }
+
+ @Override
public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
"no permission to read the sync settings");
@@ -473,6 +572,7 @@ public final class ContentService extends IContentService.Stub {
return -1;
}
+ @Override
public void setIsSyncable(Account account, String providerName, int syncable) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
"no permission to write the sync settings");
@@ -490,6 +590,7 @@ public final class ContentService extends IContentService.Stub {
}
}
+ @Override
public boolean getMasterSyncAutomatically() {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
"no permission to read the sync settings");
@@ -507,6 +608,7 @@ public final class ContentService extends IContentService.Stub {
return false;
}
+ @Override
public void setMasterSyncAutomatically(boolean flag) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
"no permission to write the sync settings");
diff --git a/services/java/com/android/server/content/SyncManager.java b/services/java/com/android/server/content/SyncManager.java
index 2da95c3..ee5b890 100644
--- a/services/java/com/android/server/content/SyncManager.java
+++ b/services/java/com/android/server/content/SyncManager.java
@@ -34,6 +34,7 @@ import android.content.ISyncContext;
import android.content.ISyncStatusObserver;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.PeriodicSync;
import android.content.ServiceConnection;
import android.content.SyncActivityTooManyDeletes;
import android.content.SyncAdapterType;
@@ -83,6 +84,7 @@ import com.google.android.collect.Sets;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -190,6 +192,7 @@ public class SyncManager {
private BroadcastReceiver mStorageIntentReceiver =
new BroadcastReceiver() {
+ @Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
@@ -210,36 +213,39 @@ public class SyncManager {
};
private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
+ @Override
public void onReceive(Context context, Intent intent) {
mSyncHandler.onBootCompleted();
}
};
private BroadcastReceiver mBackgroundDataSettingChanged = new BroadcastReceiver() {
+ @Override
public void onReceive(Context context, Intent intent) {
if (getConnectivityManager().getBackgroundDataSetting()) {
scheduleSync(null /* account */, UserHandle.USER_ALL,
SyncOperation.REASON_BACKGROUND_DATA_SETTINGS_CHANGED,
null /* authority */,
- new Bundle(), 0 /* delay */,
+ new Bundle(), 0 /* delay */, 0 /* delay */,
false /* onlyThoseWithUnknownSyncableState */);
}
}
};
private BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() {
+ @Override
public void onReceive(Context context, Intent intent) {
updateRunningAccounts();
// Kick off sync for everyone, since this was a radical account change
scheduleSync(null, UserHandle.USER_ALL, SyncOperation.REASON_ACCOUNTS_UPDATED, null,
- null, 0 /* no delay */, false);
+ null, 0 /* no delay */, 0/* no delay */, false);
}
};
private final PowerManager mPowerManager;
- // Use this as a random offset to seed all periodic syncs
+ // Use this as a random offset to seed all periodic syncs.
private int mSyncRandomOffsetMillis;
private final UserManager mUserManager;
@@ -296,6 +302,7 @@ public class SyncManager {
private BroadcastReceiver mConnectivityIntentReceiver =
new BroadcastReceiver() {
+ @Override
public void onReceive(Context context, Intent intent) {
final boolean wasConnected = mDataConnectionIsConnected;
@@ -321,6 +328,7 @@ public class SyncManager {
private BroadcastReceiver mShutdownIntentReceiver =
new BroadcastReceiver() {
+ @Override
public void onReceive(Context context, Intent intent) {
Log.w(TAG, "Writing sync state before shutdown...");
getSyncStorageEngine().writeAllState();
@@ -371,9 +379,13 @@ public class SyncManager {
SyncStorageEngine.init(context);
mSyncStorageEngine = SyncStorageEngine.getSingleton();
mSyncStorageEngine.setOnSyncRequestListener(new OnSyncRequestListener() {
+ @Override
public void onSyncRequest(Account account, int userId, int reason, String authority,
Bundle extras) {
- scheduleSync(account, userId, reason, authority, extras, 0, false);
+ scheduleSync(account, userId, reason, authority, extras,
+ 0 /* no delay */,
+ 0 /* no delay */,
+ false);
}
});
@@ -388,7 +400,7 @@ public class SyncManager {
if (!removed) {
scheduleSync(null, UserHandle.USER_ALL,
SyncOperation.REASON_SERVICE_CHANGED,
- type.authority, null, 0 /* no delay */,
+ type.authority, null, 0 /* no delay */, 0 /* no delay */,
false /* onlyThoseWithUnkownSyncableState */);
}
}
@@ -453,6 +465,7 @@ public class SyncManager {
mSyncStorageEngine.addStatusChangeListener(
ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, new ISyncStatusObserver.Stub() {
+ @Override
public void onStatusChanged(int which) {
// force the sync loop to run if the settings change
sendCheckAlarmsMessage();
@@ -526,6 +539,177 @@ public class SyncManager {
}
/**
+ * Initiate a sync using the new anonymous service API.
+ * TODO: Implement.
+ * @param cname SyncService component bound to in order to perform the sync.
+ * @param userId the id of the user whose accounts are to be synced. If userId is USER_ALL,
+ * then all users' accounts are considered.
+ * @param uid Linux uid of the application that is performing the sync.
+ * @param extras a Map of SyncAdapter-specific information to control
+ * syncs of a specific provider. Can be null.
+ * @param beforeRunTimeMillis
+ * @param runtimeMillis
+ */
+ public void scheduleSync(ComponentName cname, int userId, int uid, Bundle extras,
+ long beforeRunTimeMillis, long runtimeMillis,
+ boolean onlyThoseWithUnknownSyncableState) {
+/**
+ boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+
+ final boolean backgroundDataUsageAllowed = !mBootCompleted ||
+ getConnectivityManager().getBackgroundDataSetting();
+
+ if (extras == null) {
+ extras = new Bundle();
+ }
+ if (isLoggable) {
+ Log.e(TAG, requestedAccount + " " + extras.toString() + " " + requestedAuthority);
+ }
+ Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
+ if (expedited) {
+ runtimeMillis = -1; // this means schedule at the front of the queue
+ }
+
+ AccountAndUser[] accounts;
+ if (requestedAccount != null && userId != UserHandle.USER_ALL) {
+ accounts = new AccountAndUser[] { new AccountAndUser(requestedAccount, userId) };
+ } else {
+ // if the accounts aren't configured yet then we can't support an account-less
+ // sync request
+ accounts = mRunningAccounts;
+ if (accounts.length == 0) {
+ if (isLoggable) {
+ Log.v(TAG, "scheduleSync: no accounts configured, dropping");
+ }
+ return;
+ }
+ }
+
+ final boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);
+ final boolean manualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
+ if (manualSync) {
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
+ extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
+ }
+ final boolean ignoreSettings =
+ extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
+
+ int source;
+ if (uploadOnly) {
+ source = SyncStorageEngine.SOURCE_LOCAL;
+ } else if (manualSync) {
+ source = SyncStorageEngine.SOURCE_USER;
+ } else if (requestedAuthority == null) {
+ source = SyncStorageEngine.SOURCE_POLL;
+ } else {
+ // this isn't strictly server, since arbitrary callers can (and do) request
+ // a non-forced two-way sync on a specific url
+ source = SyncStorageEngine.SOURCE_SERVER;
+ }
+
+ for (AccountAndUser account : accounts) {
+ // Compile a list of authorities that have sync adapters.
+ // For each authority sync each account that matches a sync adapter.
+ final HashSet<String> syncableAuthorities = new HashSet<String>();
+ for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter :
+ mSyncAdapters.getAllServices(account.userId)) {
+ syncableAuthorities.add(syncAdapter.type.authority);
+ }
+
+ // if the url was specified then replace the list of authorities
+ // with just this authority or clear it if this authority isn't
+ // syncable
+ if (requestedAuthority != null) {
+ final boolean hasSyncAdapter = syncableAuthorities.contains(requestedAuthority);
+ syncableAuthorities.clear();
+ if (hasSyncAdapter) syncableAuthorities.add(requestedAuthority);
+ }
+
+ for (String authority : syncableAuthorities) {
+ int isSyncable = getIsSyncable(account.account, account.userId,
+ authority);
+ if (isSyncable == 0) {
+ continue;
+ }
+ final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
+ syncAdapterInfo = mSyncAdapters.getServiceInfo(
+ SyncAdapterType.newKey(authority, account.account.type), account.userId);
+ if (syncAdapterInfo == null) {
+ continue;
+ }
+ final boolean allowParallelSyncs = syncAdapterInfo.type.allowParallelSyncs();
+ final boolean isAlwaysSyncable = syncAdapterInfo.type.isAlwaysSyncable();
+ if (isSyncable < 0 && isAlwaysSyncable) {
+ mSyncStorageEngine.setIsSyncable(account.account, account.userId, authority, 1);
+ isSyncable = 1;
+ }
+ if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) {
+ continue;
+ }
+ if (!syncAdapterInfo.type.supportsUploading() && uploadOnly) {
+ continue;
+ }
+
+ // always allow if the isSyncable state is unknown
+ boolean syncAllowed =
+ (isSyncable < 0)
+ || ignoreSettings
+ || (backgroundDataUsageAllowed
+ && mSyncStorageEngine.getMasterSyncAutomatically(account.userId)
+ && mSyncStorageEngine.getSyncAutomatically(account.account,
+ account.userId, authority));
+ if (!syncAllowed) {
+ if (isLoggable) {
+ Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority
+ + " is not allowed, dropping request");
+ }
+ continue;
+ }
+
+ Pair<Long, Long> backoff = mSyncStorageEngine
+ .getBackoff(account.account, account.userId, authority);
+ long delayUntil = mSyncStorageEngine.getDelayUntilTime(account.account,
+ account.userId, authority);
+ final long backoffTime = backoff != null ? backoff.first : 0;
+ if (isSyncable < 0) {
+ // Initialisation sync.
+ Bundle newExtras = new Bundle();
+ newExtras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
+ if (isLoggable) {
+ Log.v(TAG, "schedule initialisation Sync:"
+ + ", delay until " + delayUntil
+ + ", run by " + 0
+ + ", source " + source
+ + ", account " + account
+ + ", authority " + authority
+ + ", extras " + newExtras);
+ }
+ scheduleSyncOperation(
+ new SyncOperation(account.account, account.userId, reason, source,
+ authority, newExtras, 0 /* immediate , 0 /* No flex time,
+ backoffTime, delayUntil, allowParallelSyncs));
+ }
+ if (!onlyThoseWithUnkownSyncableState) {
+ if (isLoggable) {
+ Log.v(TAG, "scheduleSync:"
+ + " delay until " + delayUntil
+ + " run by " + runtimeMillis
+ + " flex " + beforeRuntimeMillis
+ + ", source " + source
+ + ", account " + account
+ + ", authority " + authority
+ + ", extras " + extras);
+ }
+ scheduleSyncOperation(
+ new SyncOperation(account.account, account.userId, reason, source,
+ authority, extras, runtimeMillis, beforeRuntimeMillis,
+ backoffTime, delayUntil, allowParallelSyncs));
+ }
+ }
+ }*/
+ }
+
+ /**
* Initiate a sync. This can start a sync for all providers
* (pass null to url, set onlyTicklable to false), only those
* providers that are marked as ticklable (pass null to url,
@@ -562,22 +746,28 @@ public class SyncManager {
* @param extras a Map of SyncAdapter-specific information to control
* syncs of a specific provider. Can be null. Is ignored
* if the url is null.
- * @param delay how many milliseconds in the future to wait before performing this
- * @param onlyThoseWithUnkownSyncableState
+ * @param beforeRuntimeMillis milliseconds before runtimeMillis that this sync can run.
+ * @param runtimeMillis maximum milliseconds in the future to wait before performing sync.
+ * @param onlyThoseWithUnkownSyncableState Only sync authorities that have unknown state.
*/
public void scheduleSync(Account requestedAccount, int userId, int reason,
- String requestedAuthority, Bundle extras, long delay,
- boolean onlyThoseWithUnkownSyncableState) {
+ String requestedAuthority, Bundle extras, long beforeRuntimeMillis,
+ long runtimeMillis, boolean onlyThoseWithUnkownSyncableState) {
boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
final boolean backgroundDataUsageAllowed = !mBootCompleted ||
getConnectivityManager().getBackgroundDataSetting();
- if (extras == null) extras = new Bundle();
-
+ if (extras == null) {
+ extras = new Bundle();
+ }
+ if (isLoggable) {
+ Log.d(TAG, "one-time sync for: " + requestedAccount + " " + extras.toString() + " "
+ + requestedAuthority);
+ }
Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
if (expedited) {
- delay = -1; // this means schedule at the front of the queue
+ runtimeMillis = -1; // this means schedule at the front of the queue
}
AccountAndUser[] accounts;
@@ -682,11 +872,13 @@ public class SyncManager {
account.userId, authority);
final long backoffTime = backoff != null ? backoff.first : 0;
if (isSyncable < 0) {
+ // Initialisation sync.
Bundle newExtras = new Bundle();
newExtras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
if (isLoggable) {
- Log.v(TAG, "scheduleSync:"
- + " delay " + delay
+ Log.v(TAG, "schedule initialisation Sync:"
+ + ", delay until " + delayUntil
+ + ", run by " + 0
+ ", source " + source
+ ", account " + account
+ ", authority " + authority
@@ -694,13 +886,15 @@ public class SyncManager {
}
scheduleSyncOperation(
new SyncOperation(account.account, account.userId, reason, source,
- authority, newExtras, 0, backoffTime, delayUntil,
- allowParallelSyncs));
+ authority, newExtras, 0 /* immediate */, 0 /* No flex time*/,
+ backoffTime, delayUntil, allowParallelSyncs));
}
if (!onlyThoseWithUnkownSyncableState) {
if (isLoggable) {
Log.v(TAG, "scheduleSync:"
- + " delay " + delay
+ + " delay until " + delayUntil
+ + " run by " + runtimeMillis
+ + " flex " + beforeRuntimeMillis
+ ", source " + source
+ ", account " + account
+ ", authority " + authority
@@ -708,17 +902,23 @@ public class SyncManager {
}
scheduleSyncOperation(
new SyncOperation(account.account, account.userId, reason, source,
- authority, extras, delay, backoffTime, delayUntil,
- allowParallelSyncs));
+ authority, extras, runtimeMillis, beforeRuntimeMillis,
+ backoffTime, delayUntil, allowParallelSyncs));
}
}
}
}
+ /**
+ * Schedule sync based on local changes to a provider. Occurs within interval
+ * [LOCAL_SYNC_DELAY, 2*LOCAL_SYNC_DELAY].
+ */
public void scheduleLocalSync(Account account, int userId, int reason, String authority) {
final Bundle extras = new Bundle();
extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
- scheduleSync(account, userId, reason, authority, extras, LOCAL_SYNC_DELAY,
+ scheduleSync(account, userId, reason, authority, extras,
+ LOCAL_SYNC_DELAY /* earliest run time */,
+ 2 * LOCAL_SYNC_DELAY /* latest sync time. */,
false /* onlyThoseWithUnkownSyncableState */);
}
@@ -775,6 +975,7 @@ public class SyncManager {
}
class SyncAlarmIntentReceiver extends BroadcastReceiver {
+ @Override
public void onReceive(Context context, Intent intent) {
mHandleAlarmWakeLock.acquire();
sendSyncAlarmMessage();
@@ -943,11 +1144,13 @@ public class SyncManager {
Log.d(TAG, "retrying sync operation that failed because there was already a "
+ "sync in progress: " + operation);
}
- scheduleSyncOperation(new SyncOperation(operation.account, operation.userId,
+ scheduleSyncOperation(
+ new SyncOperation(
+ operation.account, operation.userId,
operation.reason,
operation.syncSource,
operation.authority, operation.extras,
- DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000,
+ DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000, operation.flexTime,
operation.backoff, operation.delayUntil, operation.allowParallelSyncs));
} else if (syncResult.hasSoftError()) {
if (isLoggable) {
@@ -977,7 +1180,8 @@ public class SyncManager {
final Account[] accounts = AccountManagerService.getSingleton().getAccounts(userId);
for (Account account : accounts) {
scheduleSync(account, userId, SyncOperation.REASON_USER_START, null, null,
- 0 /* no delay */, true /* onlyThoseWithUnknownSyncableState */);
+ 0 /* no delay */, 0 /* No flex */,
+ true /* onlyThoseWithUnknownSyncableState */);
}
sendCheckAlarmsMessage();
@@ -1204,7 +1408,10 @@ public class SyncManager {
synchronized (mSyncQueue) {
sb.setLength(0);
mSyncQueue.dump(sb);
+ // Dump Pending Operations.
+ getSyncStorageEngine().dumpPendingOperations(sb);
}
+
pw.println();
pw.print(sb.toString());
@@ -1271,12 +1478,15 @@ public class SyncManager {
for (int i = 0; i < settings.periodicSyncs.size(); i++) {
- final Pair<Bundle, Long> pair = settings.periodicSyncs.get(i);
- final String period = String.valueOf(pair.second);
- final String extras = pair.first.size() > 0 ? " " + pair.first.toString() : "";
- final String next = formatTime(status.getPeriodicSyncTime(i)
- + pair.second * 1000);
- table.set(row + i * 2, 12, period + extras);
+ final PeriodicSync sync = settings.periodicSyncs.get(i);
+ final String period =
+ String.format("[p:%d s, f: %d s]", sync.period, sync.flexTime);
+ final String extras =
+ sync.extras.size() > 0 ?
+ sync.extras.toString() : "Bundle[]";
+ final String next = "Next sync: " + formatTime(status.getPeriodicSyncTime(i)
+ + sync.period * 1000);
+ table.set(row + i * 2, 12, period + " " + extras);
table.set(row + i * 2 + 1, 12, next);
}
@@ -1810,6 +2020,7 @@ public class SyncManager {
super(looper);
}
+ @Override
public void handleMessage(Message msg) {
long earliestFuturePollTime = Long.MAX_VALUE;
long nextPendingSyncTime = Long.MAX_VALUE;
@@ -1827,7 +2038,7 @@ public class SyncManager {
earliestFuturePollTime = scheduleReadyPeriodicSyncs();
switch (msg.what) {
case SyncHandler.MESSAGE_CANCEL: {
- Pair<Account, String> payload = (Pair<Account, String>)msg.obj;
+ Pair<Account, String> payload = (Pair<Account, String>) msg.obj;
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CANCEL: "
+ payload.first + ", " + payload.second);
@@ -1934,6 +2145,10 @@ public class SyncManager {
* in milliseconds since boot
*/
private long scheduleReadyPeriodicSyncs() {
+ final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
+ if (isLoggable) {
+ Log.v(TAG, "scheduleReadyPeriodicSyncs");
+ }
final boolean backgroundDataUsageAllowed =
getConnectivityManager().getBackgroundDataSetting();
long earliestFuturePollTime = Long.MAX_VALUE;
@@ -1973,37 +2188,59 @@ public class SyncManager {
}
for (int i = 0, N = authorityInfo.periodicSyncs.size(); i < N; i++) {
- final Bundle extras = authorityInfo.periodicSyncs.get(i).first;
- final Long periodInMillis = authorityInfo.periodicSyncs.get(i).second * 1000;
- // Skip if the period is invalid
+ final PeriodicSync sync = authorityInfo.periodicSyncs.get(i);
+ final Bundle extras = sync.extras;
+ final Long periodInMillis = sync.period * 1000;
+ final Long flexInMillis = sync.flexTime * 1000;
+ // Skip if the period is invalid.
if (periodInMillis <= 0) {
continue;
}
- // find when this periodic sync was last scheduled to run
+ // Find when this periodic sync was last scheduled to run.
final long lastPollTimeAbsolute = status.getPeriodicSyncTime(i);
-
+ final long shiftedLastPollTimeAbsolute =
+ (0 < lastPollTimeAbsolute - mSyncRandomOffsetMillis) ?
+ (lastPollTimeAbsolute - mSyncRandomOffsetMillis) : 0;
long remainingMillis
- = periodInMillis - (shiftedNowAbsolute % periodInMillis);
-
+ = periodInMillis - (shiftedNowAbsolute % periodInMillis);
+ long timeSinceLastRunMillis
+ = (nowAbsolute - lastPollTimeAbsolute);
+ // Schedule this periodic sync to run early if it's close enough to its next
+ // runtime, and far enough from its last run time.
+ // If we are early, there will still be time remaining in this period.
+ boolean runEarly = remainingMillis <= flexInMillis
+ && timeSinceLastRunMillis > periodInMillis - flexInMillis;
+ if (isLoggable) {
+ Log.v(TAG, "sync: " + i + " for " + authorityInfo.authority + "."
+ + " period: " + (periodInMillis)
+ + " flex: " + (flexInMillis)
+ + " remaining: " + (remainingMillis)
+ + " time_since_last: " + timeSinceLastRunMillis
+ + " last poll absol: " + lastPollTimeAbsolute
+ + " last poll shifed: " + shiftedLastPollTimeAbsolute
+ + " shifted now: " + shiftedNowAbsolute
+ + " run_early: " + runEarly);
+ }
/*
* Sync scheduling strategy: Set the next periodic sync
* based on a random offset (in seconds). Also sync right
* now if any of the following cases hold and mark it as
* having been scheduled
- * Case 1: This sync is ready to run
- * now.
+ * Case 1: This sync is ready to run now.
* Case 2: If the lastPollTimeAbsolute is in the
* future, sync now and reinitialize. This can happen for
* example if the user changed the time, synced and changed
* back.
* Case 3: If we failed to sync at the last scheduled
- * time
+ * time.
+ * Case 4: This sync is close enough to the time that we can schedule it.
*/
- if (remainingMillis == periodInMillis // Case 1
+ if (runEarly // Case 4
+ || remainingMillis == periodInMillis // Case 1
|| lastPollTimeAbsolute > nowAbsolute // Case 2
- || (nowAbsolute - lastPollTimeAbsolute
- >= periodInMillis)) { // Case 3
+ || timeSinceLastRunMillis >= periodInMillis) { // Case 3
// Sync now
+
final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(
authorityInfo.account, authorityInfo.userId,
authorityInfo.authority);
@@ -2015,24 +2252,29 @@ public class SyncManager {
if (syncAdapterInfo == null) {
continue;
}
+ mSyncStorageEngine.setPeriodicSyncTime(authorityInfo.ident,
+ authorityInfo.periodicSyncs.get(i), nowAbsolute);
scheduleSyncOperation(
new SyncOperation(authorityInfo.account, authorityInfo.userId,
SyncOperation.REASON_PERIODIC,
SyncStorageEngine.SOURCE_PERIODIC,
- authorityInfo.authority, extras, 0 /* delay */,
+ authorityInfo.authority, extras,
+ 0 /* runtime */, 0 /* flex */,
backoff != null ? backoff.first : 0,
mSyncStorageEngine.getDelayUntilTime(
authorityInfo.account, authorityInfo.userId,
authorityInfo.authority),
syncAdapterInfo.type.allowParallelSyncs()));
- mSyncStorageEngine.setPeriodicSyncTime(authorityInfo.ident,
- authorityInfo.periodicSyncs.get(i), nowAbsolute);
+
+ }
+ // Compute when this periodic sync should next run.
+ long nextPollTimeAbsolute;
+ if (runEarly) {
+ // Add the time remaining so we don't get out of phase.
+ nextPollTimeAbsolute = nowAbsolute + periodInMillis + remainingMillis;
+ } else {
+ nextPollTimeAbsolute = nowAbsolute + remainingMillis;
}
- // Compute when this periodic sync should next run
- final long nextPollTimeAbsolute = nowAbsolute + remainingMillis;
-
- // remember this time if it is earlier than
- // earliestFuturePollTime
if (nextPollTimeAbsolute < earliestFuturePollTime) {
earliestFuturePollTime = nextPollTimeAbsolute;
}
@@ -2044,10 +2286,9 @@ public class SyncManager {
}
// convert absolute time to elapsed time
- return SystemClock.elapsedRealtime()
- + ((earliestFuturePollTime < nowAbsolute)
- ? 0
- : (earliestFuturePollTime - nowAbsolute));
+ return SystemClock.elapsedRealtime() +
+ ((earliestFuturePollTime < nowAbsolute) ?
+ 0 : (earliestFuturePollTime - nowAbsolute));
}
private long maybeStartNextSyncLocked() {
@@ -2097,8 +2338,8 @@ public class SyncManager {
Log.v(TAG, "build the operation array, syncQueue size is "
+ mSyncQueue.getOperations().size());
}
- final Iterator<SyncOperation> operationIterator = mSyncQueue.getOperations()
- .iterator();
+ final Iterator<SyncOperation> operationIterator =
+ mSyncQueue.getOperations().iterator();
final ActivityManager activityManager
= (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
@@ -2106,40 +2347,52 @@ public class SyncManager {
while (operationIterator.hasNext()) {
final SyncOperation op = operationIterator.next();
- // drop the sync if the account of this operation no longer exists
+ // Drop the sync if the account of this operation no longer exists.
if (!containsAccountAndUser(accounts, op.account, op.userId)) {
operationIterator.remove();
mSyncStorageEngine.deleteFromPending(op.pendingOperation);
+ if (isLoggable) {
+ Log.v(TAG, " Dropping sync operation: account doesn't exist.");
+ }
continue;
}
- // drop this sync request if it isn't syncable
+ // Drop this sync request if it isn't syncable.
int syncableState = getIsSyncable(
op.account, op.userId, op.authority);
if (syncableState == 0) {
operationIterator.remove();
mSyncStorageEngine.deleteFromPending(op.pendingOperation);
+ if (isLoggable) {
+ Log.v(TAG, " Dropping sync operation: isSyncable == 0.");
+ }
continue;
}
- // if the user in not running, drop the request
+ // If the user is not running, drop the request.
if (!activityManager.isUserRunning(op.userId)) {
final UserInfo userInfo = mUserManager.getUserInfo(op.userId);
if (userInfo == null) {
removedUsers.add(op.userId);
}
+ if (isLoggable) {
+ Log.v(TAG, " Dropping sync operation: user not running.");
+ }
continue;
}
- // if the next run time is in the future, meaning there are no syncs ready
- // to run, return the time
- if (op.effectiveRunTime > now) {
+ // If the next run time is in the future, even given the flexible scheduling,
+ // return the time.
+ if (op.effectiveRunTime - op.flexTime > now) {
if (nextReadyToRunTime > op.effectiveRunTime) {
nextReadyToRunTime = op.effectiveRunTime;
}
+ if (isLoggable) {
+ Log.v(TAG, " Dropping sync operation: Sync too far in future.");
+ }
continue;
}
-
+ // TODO: change this behaviour for non-registered syncs.
final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
syncAdapterInfo = mSyncAdapters.getServiceInfo(
SyncAdapterType.newKey(op.authority, op.account.type), op.userId);
@@ -2180,7 +2433,7 @@ public class SyncManager {
}
// find the next operation to dispatch, if one is ready
- // iterate from the top, keep issuing (while potentially cancelling existing syncs)
+ // iterate from the top, keep issuing (while potentially canceling existing syncs)
// until the quotas are filled.
// once the quotas are filled iterate once more to find when the next one would be
// (also considering pre-emption reasons).
@@ -2460,11 +2713,13 @@ public class SyncManager {
}
if (syncResult != null && syncResult.fullSyncRequested) {
- scheduleSyncOperation(new SyncOperation(syncOperation.account, syncOperation.userId,
- syncOperation.reason,
- syncOperation.syncSource, syncOperation.authority, new Bundle(), 0,
- syncOperation.backoff, syncOperation.delayUntil,
- syncOperation.allowParallelSyncs));
+ scheduleSyncOperation(
+ new SyncOperation(syncOperation.account, syncOperation.userId,
+ syncOperation.reason,
+ syncOperation.syncSource, syncOperation.authority, new Bundle(),
+ 0 /* delay */, 0 /* flex */,
+ syncOperation.backoff, syncOperation.delayUntil,
+ syncOperation.allowParallelSyncs));
}
// no need to schedule an alarm, as that will be done by our caller.
}
@@ -2637,6 +2892,8 @@ public class SyncManager {
final boolean alarmIsActive = mAlarmScheduleTime != null;
final boolean needAlarm = alarmTime != Long.MAX_VALUE;
if (needAlarm) {
+ // Need the alarm if it's currently not set, or if our time is before the currently
+ // set time.
if (!alarmIsActive || alarmTime < mAlarmScheduleTime) {
shouldSet = true;
}
@@ -2644,7 +2901,7 @@ public class SyncManager {
shouldCancel = alarmIsActive;
}
- // set or cancel the alarm as directed
+ // Set or cancel the alarm as directed.
ensureAlarmService();
if (shouldSet) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
diff --git a/services/java/com/android/server/content/SyncOperation.java b/services/java/com/android/server/content/SyncOperation.java
index eaad982..b688535 100644
--- a/services/java/com/android/server/content/SyncOperation.java
+++ b/services/java/com/android/server/content/SyncOperation.java
@@ -18,13 +18,18 @@ package com.android.server.content;
import android.accounts.Account;
import android.content.pm.PackageManager;
+import android.content.ComponentName;
import android.content.ContentResolver;
+import android.content.SyncRequest;
import android.os.Bundle;
import android.os.SystemClock;
+import android.util.Pair;
/**
* Value type that represents a sync operation.
- * @hide
+ * TODO: This is the class to flesh out with all the scheduling data - metered/unmetered,
+ * transfer-size, etc.
+ * {@hide}
*/
public class SyncOperation implements Comparable {
public static final int REASON_BACKGROUND_DATA_SETTINGS_CHANGED = -1;
@@ -32,7 +37,9 @@ public class SyncOperation implements Comparable {
public static final int REASON_SERVICE_CHANGED = -3;
public static final int REASON_PERIODIC = -4;
public static final int REASON_IS_SYNCABLE = -5;
+ /** Sync started because it has just been set to sync automatically. */
public static final int REASON_SYNC_AUTO = -6;
+ /** Sync started because master sync automatically has been set to true. */
public static final int REASON_MASTER_SYNC_AUTO = -7;
public static final int REASON_USER_START = -8;
@@ -47,75 +54,143 @@ public class SyncOperation implements Comparable {
"UserStart",
};
+ /** Account info to identify a SyncAdapter registered with the system. */
public final Account account;
+ /** Authority info to identify a SyncAdapter registered with the system. */
+ public final String authority;
+ /** Service to which this operation will bind to perform the sync. */
+ public final ComponentName service;
public final int userId;
public final int reason;
public int syncSource;
- public String authority;
public final boolean allowParallelSyncs;
public Bundle extras;
public final String key;
- public long earliestRunTime;
public boolean expedited;
public SyncStorageEngine.PendingOperation pendingOperation;
+ /** Elapsed real time in millis at which to run this sync. */
+ public long latestRunTime;
+ /** Set by the SyncManager in order to delay retries. */
public Long backoff;
+ /** Specified by the adapter to delay subsequent sync operations. */
public long delayUntil;
+ /**
+ * Elapsed real time in millis when this sync will be run.
+ * Depends on max(backoff, latestRunTime, and delayUntil).
+ */
public long effectiveRunTime;
+ /** Amount of time before {@link effectiveRunTime} from which this sync can run. */
+ public long flexTime;
public SyncOperation(Account account, int userId, int reason, int source, String authority,
- Bundle extras, long delayInMs, long backoff, long delayUntil,
- boolean allowParallelSyncs) {
+ Bundle extras, long runTimeFromNow, long flexTime, long backoff,
+ long delayUntil, boolean allowParallelSyncs) {
+ this.service = null;
this.account = account;
+ this.authority = authority;
this.userId = userId;
this.reason = reason;
this.syncSource = source;
- this.authority = authority;
this.allowParallelSyncs = allowParallelSyncs;
this.extras = new Bundle(extras);
- removeFalseExtra(ContentResolver.SYNC_EXTRAS_UPLOAD);
- removeFalseExtra(ContentResolver.SYNC_EXTRAS_MANUAL);
- removeFalseExtra(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS);
- removeFalseExtra(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
- removeFalseExtra(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY);
- removeFalseExtra(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS);
- removeFalseExtra(ContentResolver.SYNC_EXTRAS_EXPEDITED);
- removeFalseExtra(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS);
+ cleanBundle(this.extras);
this.delayUntil = delayUntil;
this.backoff = backoff;
final long now = SystemClock.elapsedRealtime();
- if (delayInMs < 0) {
+ // Check the extras bundle. Must occur after we set the internal bundle.
+ if (runTimeFromNow < 0 || isExpedited()) {
this.expedited = true;
- this.earliestRunTime = now;
+ this.latestRunTime = now;
+ this.flexTime = 0;
} else {
this.expedited = false;
- this.earliestRunTime = now + delayInMs;
+ this.latestRunTime = now + runTimeFromNow;
+ this.flexTime = flexTime;
}
updateEffectiveRunTime();
this.key = toKey();
}
- private void removeFalseExtra(String extraName) {
- if (!extras.getBoolean(extraName, false)) {
- extras.remove(extraName);
+ public SyncOperation(SyncRequest request, int userId, int reason, int source, long backoff,
+ long delayUntil, boolean allowParallelSyncs) {
+ if (request.hasAuthority()) {
+ Pair<Account, String> providerInfo = request.getProviderInfo();
+ this.account = providerInfo.first;
+ this.authority = providerInfo.second;
+ this.service = null;
+ } else {
+ this.service = request.getService();
+ this.account = null;
+ this.authority = null;
}
+ this.userId = userId;
+ this.reason = reason;
+ this.syncSource = source;
+ this.allowParallelSyncs = allowParallelSyncs;
+ this.extras = new Bundle(extras);
+ cleanBundle(this.extras);
+ this.delayUntil = delayUntil;
+ this.backoff = backoff;
+ final long now = SystemClock.elapsedRealtime();
+ if (request.isExpedited()) {
+ this.expedited = true;
+ this.latestRunTime = now;
+ this.flexTime = 0;
+ } else {
+ this.expedited = false;
+ this.latestRunTime = now + (request.getSyncRunTime() * 1000);
+ this.flexTime = request.getSyncFlexTime() * 1000;
+ }
+ updateEffectiveRunTime();
+ this.key = toKey();
}
+ /**
+ * Make sure the bundle attached to this SyncOperation doesn't have unnecessary
+ * flags set.
+ * @param bundle to clean.
+ */
+ private void cleanBundle(Bundle bundle) {
+ removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_UPLOAD);
+ removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_MANUAL);
+ removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS);
+ removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
+ removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY);
+ removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS);
+ removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_EXPEDITED);
+ removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS);
+ removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_ALLOW_METERED);
+
+ // Remove Config data.
+ bundle.remove(ContentResolver.SYNC_EXTRAS_EXPECTED_UPLOAD);
+ bundle.remove(ContentResolver.SYNC_EXTRAS_EXPECTED_DOWNLOAD);
+ }
+
+ private void removeFalseExtra(Bundle bundle, String extraName) {
+ if (!bundle.getBoolean(extraName, false)) {
+ bundle.remove(extraName);
+ }
+ }
+
+ /** Only used to immediately reschedule a sync. */
SyncOperation(SyncOperation other) {
+ this.service = other.service;
this.account = other.account;
+ this.authority = other.authority;
this.userId = other.userId;
this.reason = other.reason;
this.syncSource = other.syncSource;
- this.authority = other.authority;
this.extras = new Bundle(other.extras);
this.expedited = other.expedited;
- this.earliestRunTime = SystemClock.elapsedRealtime();
+ this.latestRunTime = SystemClock.elapsedRealtime();
+ this.flexTime = 0L;
this.backoff = other.backoff;
- this.delayUntil = other.delayUntil;
this.allowParallelSyncs = other.allowParallelSyncs;
this.updateEffectiveRunTime();
this.key = toKey();
}
+ @Override
public String toString() {
return dump(null, true);
}
@@ -131,8 +206,8 @@ public class SyncOperation implements Comparable {
.append(authority)
.append(", ")
.append(SyncStorageEngine.SOURCES[syncSource])
- .append(", earliestRunTime ")
- .append(earliestRunTime);
+ .append(", latestRunTime ")
+ .append(latestRunTime);
if (expedited) {
sb.append(", EXPEDITED");
}
@@ -170,23 +245,38 @@ public class SyncOperation implements Comparable {
}
}
+ public boolean isMetered() {
+ return extras.getBoolean(ContentResolver.SYNC_EXTRAS_ALLOW_METERED, false);
+ }
+
public boolean isInitialization() {
return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
}
public boolean isExpedited() {
- return extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
+ return extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false) || expedited;
}
public boolean ignoreBackoff() {
return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false);
}
+ /** Changed in V3. */
private String toKey() {
StringBuilder sb = new StringBuilder();
- sb.append("authority: ").append(authority);
- sb.append(" account {name=" + account.name + ", user=" + userId + ", type=" + account.type
- + "}");
+ if (service == null) {
+ sb.append("authority: ").append(authority);
+ sb.append(" account {name=" + account.name + ", user=" + userId + ", type=" + account.type
+ + "}");
+ } else {
+ sb.append("service {package=" )
+ .append(service.getPackageName())
+ .append(" user=")
+ .append(userId)
+ .append(", class=")
+ .append(service.getClassName())
+ .append("}");
+ }
sb.append(" extras: ");
extrasToStringBuilder(extras, sb);
return sb.toString();
@@ -200,25 +290,40 @@ public class SyncOperation implements Comparable {
sb.append("]");
}
+ /**
+ * Update the effective run time of this Operation based on latestRunTime (specified at
+ * creation time of sync), delayUntil (specified by SyncAdapter), or backoff (specified by
+ * SyncManager on soft failures).
+ */
public void updateEffectiveRunTime() {
- effectiveRunTime = ignoreBackoff()
- ? earliestRunTime
- : Math.max(
- Math.max(earliestRunTime, delayUntil),
- backoff);
+ // Regardless of whether we're in backoff or honouring a delayUntil, we still incorporate
+ // the flex time provided by the developer.
+ effectiveRunTime = ignoreBackoff() ?
+ latestRunTime :
+ Math.max(Math.max(latestRunTime, delayUntil), backoff);
}
+ /**
+ * If two SyncOperation intervals are disjoint, the smaller is the interval that occurs before.
+ * If the intervals overlap, the two are considered equal.
+ */
+ @Override
public int compareTo(Object o) {
- SyncOperation other = (SyncOperation)o;
-
+ SyncOperation other = (SyncOperation) o;
if (expedited != other.expedited) {
return expedited ? -1 : 1;
}
-
- if (effectiveRunTime == other.effectiveRunTime) {
+ long x1 = effectiveRunTime - flexTime;
+ long y1 = effectiveRunTime;
+ long x2 = other.effectiveRunTime - other.flexTime;
+ long y2 = other.effectiveRunTime;
+ // Overlapping intervals.
+ if ((x1 <= y2 && x1 >= x2) || (x2 <= y1 && x2 >= x1)) {
return 0;
}
-
- return effectiveRunTime < other.effectiveRunTime ? -1 : 1;
+ if (x1 < x2 && y1 < x2) {
+ return -1;
+ }
+ return 1;
}
}
diff --git a/services/java/com/android/server/content/SyncQueue.java b/services/java/com/android/server/content/SyncQueue.java
index 951e92c..6f3fe6e 100644
--- a/services/java/com/android/server/content/SyncQueue.java
+++ b/services/java/com/android/server/content/SyncQueue.java
@@ -73,7 +73,7 @@ public class SyncQueue {
}
SyncOperation syncOperation = new SyncOperation(
op.account, op.userId, op.reason, op.syncSource, op.authority, op.extras,
- 0 /* delay */, backoff != null ? backoff.first : 0,
+ 0 /* delay */, 0 /* flex */, backoff != null ? backoff.first : 0,
mSyncStorageEngine.getDelayUntilTime(op.account, op.userId, op.authority),
syncAdapterInfo.type.allowParallelSyncs());
syncOperation.expedited = op.expedited;
@@ -86,35 +86,40 @@ public class SyncQueue {
return add(operation, null /* this is not coming from the database */);
}
+ /**
+ * Adds a SyncOperation to the queue and creates a PendingOperation object to track that sync.
+ * If an operation is added that already exists, the existing operation is updated if the newly
+ * added operation occurs before (or the interval overlaps).
+ */
private boolean add(SyncOperation operation,
SyncStorageEngine.PendingOperation pop) {
- // - if an operation with the same key exists and this one should run earlier,
- // update the earliestRunTime of the existing to the new time
- // - if an operation with the same key exists and if this one should run
- // later, ignore it
- // - if no operation exists then add the new one
+ // If an operation with the same key exists and this one should run sooner/overlaps,
+ // replace the run interval of the existing operation with this new one.
+ // Complications: what if the existing operation is expedited but the new operation has an
+ // earlier run time? Will not be a problem for periodic syncs (no expedited flag), and for
+ // one-off syncs we only change it if the new sync is sooner.
final String operationKey = operation.key;
final SyncOperation existingOperation = mOperationsMap.get(operationKey);
if (existingOperation != null) {
boolean changed = false;
- if (existingOperation.expedited == operation.expedited) {
- final long newRunTime =
- Math.min(existingOperation.earliestRunTime, operation.earliestRunTime);
- if (existingOperation.earliestRunTime != newRunTime) {
- existingOperation.earliestRunTime = newRunTime;
- changed = true;
- }
- } else {
- if (operation.expedited) {
- existingOperation.expedited = true;
- changed = true;
- }
+ if (operation.compareTo(existingOperation) <= 0 ) {
+ existingOperation.expedited = operation.expedited;
+ long newRunTime =
+ Math.min(existingOperation.latestRunTime, operation.latestRunTime);
+ // Take smaller runtime.
+ existingOperation.latestRunTime = newRunTime;
+ // Take newer flextime.
+ existingOperation.flexTime = operation.flexTime;
+ changed = true;
}
return changed;
}
operation.pendingOperation = pop;
+ // Don't update the PendingOp if one already exists. This really is just a placeholder,
+ // no actual scheduling info is placed here.
+ // TODO: Change this to support service components.
if (operation.pendingOperation == null) {
pop = new SyncStorageEngine.PendingOperation(
operation.account, operation.userId, operation.reason, operation.syncSource,
diff --git a/services/java/com/android/server/content/SyncStorageEngine.java b/services/java/com/android/server/content/SyncStorageEngine.java
index c4dc575..0b99fca 100644
--- a/services/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/java/com/android/server/content/SyncStorageEngine.java
@@ -18,6 +18,7 @@ package com.android.server.content;
import android.accounts.Account;
import android.accounts.AccountAndUser;
+import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ISyncStatusObserver;
@@ -44,7 +45,6 @@ import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastXmlSerializer;
-import com.android.server.content.SyncStorageEngine.AuthorityInfo;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -70,8 +70,8 @@ import java.util.TimeZone;
public class SyncStorageEngine extends Handler {
private static final String TAG = "SyncManager";
- private static final boolean DEBUG = false;
- private static final boolean DEBUG_FILE = false;
+ private static final boolean DEBUG = true;
+ private static final boolean DEBUG_FILE = true;
private static final String XML_ATTR_NEXT_AUTHORITY_ID = "nextAuthorityId";
private static final String XML_ATTR_LISTEN_FOR_TICKLES = "listen-for-tickles";
@@ -80,8 +80,15 @@ public class SyncStorageEngine extends Handler {
private static final String XML_ATTR_USER = "user";
private static final String XML_TAG_LISTEN_FOR_TICKLES = "listenForTickles";
+ /** Default time for a periodic sync. */
private static final long DEFAULT_POLL_FREQUENCY_SECONDS = 60 * 60 * 24; // One day
+ /** Percentage of period that is flex by default, if no flex is set. */
+ private static final double DEFAULT_FLEX_PERCENT_SYNC = 0.04;
+
+ /** Lower bound on sync time from which we assign a default flex time. */
+ private static final long DEFAULT_MIN_FLEX_ALLOWED_SECS = 5;
+
@VisibleForTesting
static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4;
@@ -154,12 +161,13 @@ public class SyncStorageEngine extends Handler {
final int syncSource;
final String authority;
final Bundle extras; // note: read-only.
+ final ComponentName serviceName;
final boolean expedited;
int authorityId;
byte[] flatExtras;
- PendingOperation(Account account, int userId, int reason,int source,
+ PendingOperation(Account account, int userId, int reason, int source,
String authority, Bundle extras, boolean expedited) {
this.account = account;
this.userId = userId;
@@ -169,6 +177,7 @@ public class SyncStorageEngine extends Handler {
this.extras = extras != null ? new Bundle(extras) : extras;
this.expedited = expedited;
this.authorityId = -1;
+ this.serviceName = null;
}
PendingOperation(PendingOperation other) {
@@ -180,6 +189,7 @@ public class SyncStorageEngine extends Handler {
this.extras = other.extras;
this.authorityId = other.authorityId;
this.expedited = other.expedited;
+ this.serviceName = other.serviceName;
}
}
@@ -194,6 +204,7 @@ public class SyncStorageEngine extends Handler {
}
public static class AuthorityInfo {
+ final ComponentName service;
final Account account;
final int userId;
final String authority;
@@ -203,7 +214,7 @@ public class SyncStorageEngine extends Handler {
long backoffTime;
long backoffDelay;
long delayUntil;
- final ArrayList<Pair<Bundle, Long>> periodicSyncs;
+ final ArrayList<PeriodicSync> periodicSyncs;
/**
* Copy constructor for making deep-ish copies. Only the bundles stored
@@ -215,30 +226,70 @@ public class SyncStorageEngine extends Handler {
account = toCopy.account;
userId = toCopy.userId;
authority = toCopy.authority;
+ service = toCopy.service;
ident = toCopy.ident;
enabled = toCopy.enabled;
syncable = toCopy.syncable;
backoffTime = toCopy.backoffTime;
backoffDelay = toCopy.backoffDelay;
delayUntil = toCopy.delayUntil;
- periodicSyncs = new ArrayList<Pair<Bundle, Long>>();
- for (Pair<Bundle, Long> sync : toCopy.periodicSyncs) {
+ periodicSyncs = new ArrayList<PeriodicSync>();
+ for (PeriodicSync sync : toCopy.periodicSyncs) {
// Still not a perfect copy, because we are just copying the mappings.
- periodicSyncs.add(Pair.create(new Bundle(sync.first), sync.second));
+ periodicSyncs.add(new PeriodicSync(sync));
}
}
+ /**
+ * Create an authority with one periodic sync scheduled with an empty bundle and syncing
+ * every day. An empty bundle is considered equal to any other bundle see
+ * {@link PeriodicSync.syncExtrasEquals}.
+ * @param account Account that this authority syncs.
+ * @param userId which user this sync is registered for.
+ * @param userId user for which this authority is registered.
+ * @param ident id of this authority.
+ */
AuthorityInfo(Account account, int userId, String authority, int ident) {
this.account = account;
this.userId = userId;
this.authority = authority;
+ this.service = null;
this.ident = ident;
enabled = SYNC_ENABLED_DEFAULT;
syncable = -1; // default to "unknown"
backoffTime = -1; // if < 0 then we aren't in backoff mode
backoffDelay = -1; // if < 0 then we aren't in backoff mode
- periodicSyncs = new ArrayList<Pair<Bundle, Long>>();
- periodicSyncs.add(Pair.create(new Bundle(), DEFAULT_POLL_FREQUENCY_SECONDS));
+ periodicSyncs = new ArrayList<PeriodicSync>();
+ // Old version adds one periodic sync a day.
+ periodicSyncs.add(new PeriodicSync(account, authority,
+ new Bundle(),
+ DEFAULT_POLL_FREQUENCY_SECONDS,
+ calculateDefaultFlexTime(DEFAULT_POLL_FREQUENCY_SECONDS)));
+ }
+
+ /**
+ * Create an authority with one periodic sync scheduled with an empty bundle and syncing
+ * every day using a sync service.
+ * @param cname sync service identifier.
+ * @param userId user for which this authority is registered.
+ * @param ident id of this authority.
+ */
+ AuthorityInfo(ComponentName cname, int userId, int ident) {
+ this.account = null;
+ this.userId = userId;
+ this.authority = null;
+ this.service = cname;
+ this.ident = ident;
+ // Sync service is always enabled.
+ enabled = true;
+ syncable = -1; // default to "unknown"
+ backoffTime = -1; // if < 0 then we aren't in backoff mode
+ backoffDelay = -1; // if < 0 then we aren't in backoff mode
+ periodicSyncs = new ArrayList<PeriodicSync>();
+ periodicSyncs.add(new PeriodicSync(account, authority,
+ new Bundle(),
+ DEFAULT_POLL_FREQUENCY_SECONDS,
+ calculateDefaultFlexTime(DEFAULT_POLL_FREQUENCY_SECONDS)));
}
}
@@ -304,6 +355,10 @@ public class SyncStorageEngine extends Handler {
private final RemoteCallbackList<ISyncStatusObserver> mChangeListeners
= new RemoteCallbackList<ISyncStatusObserver>();
+ /** Reverse mapping for component name -> <userid -> authority id>. */
+ private final HashMap<ComponentName, SparseArray<AuthorityInfo>> mServices =
+ new HashMap<ComponentName, SparseArray<AuthorityInfo>>();
+
private int mNextAuthorityId = 0;
// We keep 4 weeks of stats.
@@ -436,6 +491,28 @@ public class SyncStorageEngine extends Handler {
}
}
+ /**
+ * Figure out a reasonable flex time for cases where none is provided (old api calls).
+ * @param syncTimeSeconds requested sync time from now.
+ * @return amount of seconds before syncTimeSeconds that the sync can occur.
+ * I.e.
+ * earliest_sync_time = syncTimeSeconds - calculateDefaultFlexTime(syncTimeSeconds)
+ * The flex time is capped at a percentage of the {@link DEFAULT_POLL_FREQUENCY_SECONDS}.
+ */
+ public static long calculateDefaultFlexTime(long syncTimeSeconds) {
+ if (syncTimeSeconds < DEFAULT_MIN_FLEX_ALLOWED_SECS) {
+ // Small enough sync request time that we don't add flex time - developer probably
+ // wants to wait for an operation to occur before syncing so we honour the
+ // request time.
+ return 0L;
+ } else if (syncTimeSeconds < DEFAULT_POLL_FREQUENCY_SECONDS) {
+ return (long) (syncTimeSeconds * DEFAULT_FLEX_PERCENT_SYNC);
+ } else {
+ // Large enough sync request time that we cap the flex time.
+ return (long) (DEFAULT_POLL_FREQUENCY_SECONDS * DEFAULT_FLEX_PERCENT_SYNC);
+ }
+ }
+
private void reportChange(int which) {
ArrayList<ISyncStatusObserver> reports = null;
synchronized (mAuthorities) {
@@ -553,8 +630,8 @@ public class SyncStorageEngine extends Handler {
+ ", user " + userId + " -> " + syncable);
}
synchronized (mAuthorities) {
- AuthorityInfo authority = getOrCreateAuthorityLocked(account, userId, providerName, -1,
- false);
+ AuthorityInfo authority =
+ getOrCreateAuthorityLocked(account, userId, providerName, -1, false);
if (authority.syncable == syncable) {
if (DEBUG) {
Log.d(TAG, "setIsSyncable: already set to " + syncable + ", doing nothing");
@@ -689,62 +766,65 @@ public class SyncStorageEngine extends Handler {
}
}
- private void updateOrRemovePeriodicSync(Account account, int userId, String providerName,
- Bundle extras,
- long period, boolean add) {
- if (period <= 0) {
- period = 0;
- }
- if (extras == null) {
- extras = new Bundle();
- }
+ private void updateOrRemovePeriodicSync(PeriodicSync toUpdate, int userId, boolean add) {
if (DEBUG) {
- Log.v(TAG, "addOrRemovePeriodicSync: " + account + ", user " + userId
- + ", provider " + providerName
- + " -> period " + period + ", extras " + extras);
+ Log.v(TAG, "addOrRemovePeriodicSync: " + toUpdate.account + ", user " + userId
+ + ", provider " + toUpdate.authority
+ + " -> period " + toUpdate.period + ", extras " + toUpdate.extras);
}
synchronized (mAuthorities) {
+ if (toUpdate.period <= 0 && add) {
+ Log.e(TAG, "period < 0, should never happen in updateOrRemovePeriodicSync: add-" + add);
+ }
+ if (toUpdate.extras == null) {
+ Log.e(TAG, "period < 0, should never happen in updateOrRemovePeriodicSync: add-" + add);
+ }
try {
AuthorityInfo authority =
- getOrCreateAuthorityLocked(account, userId, providerName, -1, false);
+ getOrCreateAuthorityLocked(toUpdate.account, userId, toUpdate.authority,
+ -1, false);
if (add) {
- // add this periodic sync if one with the same extras doesn't already
- // exist in the periodicSyncs array
+ // add this periodic sync if an equivalent periodic doesn't already exist.
boolean alreadyPresent = false;
for (int i = 0, N = authority.periodicSyncs.size(); i < N; i++) {
- Pair<Bundle, Long> syncInfo = authority.periodicSyncs.get(i);
- final Bundle existingExtras = syncInfo.first;
- if (PeriodicSync.syncExtrasEquals(existingExtras, extras)) {
- if (syncInfo.second == period) {
+ PeriodicSync syncInfo = authority.periodicSyncs.get(i);
+ if (PeriodicSync.syncExtrasEquals(
+ toUpdate.extras,
+ syncInfo.extras)) {
+ if (toUpdate.period == syncInfo.period &&
+ toUpdate.flexTime == syncInfo.flexTime) {
+ // Absolutely the same.
return;
}
- authority.periodicSyncs.set(i, Pair.create(extras, period));
+ authority.periodicSyncs.set(i, new PeriodicSync(toUpdate));
alreadyPresent = true;
break;
}
}
- // if we added an entry to the periodicSyncs array also add an entry to
- // the periodic syncs status to correspond to it
+ // If we added an entry to the periodicSyncs array also add an entry to
+ // the periodic syncs status to correspond to it.
if (!alreadyPresent) {
- authority.periodicSyncs.add(Pair.create(extras, period));
+ authority.periodicSyncs.add(new PeriodicSync(toUpdate));
SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
status.setPeriodicSyncTime(authority.periodicSyncs.size() - 1, 0);
}
} else {
- // remove any periodic syncs that match the authority and extras
+ // Remove any periodic syncs that match the authority and extras.
SyncStatusInfo status = mSyncStatus.get(authority.ident);
boolean changed = false;
- Iterator<Pair<Bundle, Long>> iterator = authority.periodicSyncs.iterator();
+ Iterator<PeriodicSync> iterator = authority.periodicSyncs.iterator();
int i = 0;
while (iterator.hasNext()) {
- Pair<Bundle, Long> syncInfo = iterator.next();
- if (PeriodicSync.syncExtrasEquals(syncInfo.first, extras)) {
+ PeriodicSync syncInfo = iterator.next();
+ if (PeriodicSync.syncExtrasEquals(syncInfo.extras, toUpdate.extras)) {
iterator.remove();
changed = true;
- // if we removed an entry from the periodicSyncs array also
+ // If we removed an entry from the periodicSyncs array also
// remove the corresponding entry from the status
if (status != null) {
status.removePeriodicSyncTime(i);
+ } else {
+ Log.e(TAG, "Tried removing sync status on remove periodic sync but did not find it.");
}
} else {
i++;
@@ -763,16 +843,12 @@ public class SyncStorageEngine extends Handler {
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
}
- public void addPeriodicSync(Account account, int userId, String providerName, Bundle extras,
- long pollFrequency) {
- updateOrRemovePeriodicSync(account, userId, providerName, extras, pollFrequency,
- true /* add */);
+ public void addPeriodicSync(PeriodicSync toAdd, int userId) {
+ updateOrRemovePeriodicSync(toAdd, userId, true /* add */);
}
- public void removePeriodicSync(Account account, int userId, String providerName,
- Bundle extras) {
- updateOrRemovePeriodicSync(account, userId, providerName, extras, 0 /* period, ignored */,
- false /* remove */);
+ public void removePeriodicSync(PeriodicSync toRemove, int userId) {
+ updateOrRemovePeriodicSync(toRemove, userId, false /* remove */);
}
public List<PeriodicSync> getPeriodicSyncs(Account account, int userId, String providerName) {
@@ -781,9 +857,9 @@ public class SyncStorageEngine extends Handler {
AuthorityInfo authority = getAuthorityLocked(account, userId, providerName,
"getPeriodicSyncs");
if (authority != null) {
- for (Pair<Bundle, Long> item : authority.periodicSyncs) {
- syncs.add(new PeriodicSync(account, providerName, item.first,
- item.second));
+ for (PeriodicSync item : authority.periodicSyncs) {
+ // Copy and send out. Necessary for thread-safety although it's parceled.
+ syncs.add(new PeriodicSync(item));
}
}
}
@@ -866,7 +942,7 @@ public class SyncStorageEngine extends Handler {
op = new PendingOperation(op);
op.authorityId = authority.ident;
mPendingOperations.add(op);
- appendPendingOperationLocked(op);
+ writePendingOperationsLocked();
SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
status.pending = true;
@@ -876,6 +952,14 @@ public class SyncStorageEngine extends Handler {
return op;
}
+ /**
+ * Remove from list of pending operations. If successful, search through list for matching
+ * authorities. If there are no more pending syncs for the same authority/account/userid,
+ * update the SyncStatusInfo for that authority(authority here is the internal representation
+ * of a 'sync operation'.
+ * @param op
+ * @return
+ */
public boolean deleteFromPending(PendingOperation op) {
boolean res = false;
synchronized (mAuthorities) {
@@ -898,7 +982,7 @@ public class SyncStorageEngine extends Handler {
AuthorityInfo authority = getAuthorityLocked(op.account, op.userId, op.authority,
"deleteFromPending");
if (authority != null) {
- if (DEBUG) Log.v(TAG, "removing - " + authority);
+ if (DEBUG) Log.v(TAG, "removing - " + authority.toString());
final int N = mPendingOperations.size();
boolean morePending = false;
for (int i=0; i<N; i++) {
@@ -1391,6 +1475,65 @@ public class SyncStorageEngine extends Handler {
return authority;
}
+ /**
+ * Retrieve an authority, returning null if one does not exist.
+ *
+ * @param service The service name used for this sync.
+ * @param userId The user for whom this sync is scheduled.
+ * @param tag If non-null, this will be used in a log message if the
+ * requested authority does not exist.
+ */
+ private AuthorityInfo getAuthorityLocked(ComponentName service, int userId, String tag) {
+ AuthorityInfo authority = mServices.get(service).get(userId);
+ if (authority == null) {
+ if (tag != null) {
+ if (DEBUG) {
+ Log.v(TAG, tag + " No authority info found for " + service + " for user "
+ + userId);
+ }
+ }
+ return null;
+ }
+ return authority;
+ }
+
+ /**
+ * @param cname identifier for the service.
+ * @param userId for the syncs corresponding to this authority.
+ * @param ident unique identifier for authority. -1 for none.
+ * @param doWrite if true, update the accounts.xml file on the disk.
+ * @return the authority that corresponds to the provided sync service, creating it if none
+ * exists.
+ */
+ private AuthorityInfo getOrCreateAuthorityLocked(ComponentName cname, int userId, int ident,
+ boolean doWrite) {
+ SparseArray<AuthorityInfo> aInfo = mServices.get(cname);
+ if (aInfo == null) {
+ aInfo = new SparseArray<AuthorityInfo>();
+ mServices.put(cname, aInfo);
+ }
+ AuthorityInfo authority = aInfo.get(userId);
+ if (authority == null) {
+ if (ident < 0) {
+ ident = mNextAuthorityId;
+ mNextAuthorityId++;
+ doWrite = true;
+ }
+ if (DEBUG) {
+ Log.v(TAG, "created a new AuthorityInfo for " + cname.getPackageName()
+ + ", " + cname.getClassName()
+ + ", user: " + userId);
+ }
+ authority = new AuthorityInfo(cname, userId, ident);
+ aInfo.put(userId, authority);
+ mAuthorities.put(ident, authority);
+ if (doWrite) {
+ writeAccountInfoLocked();
+ }
+ }
+ return authority;
+ }
+
private AuthorityInfo getOrCreateAuthorityLocked(Account accountName, int userId,
String authorityName, int ident, boolean doWrite) {
AccountAndUser au = new AccountAndUser(accountName, userId);
@@ -1441,22 +1584,20 @@ public class SyncStorageEngine extends Handler {
* authority id and target periodic sync
*/
public void setPeriodicSyncTime(
- int authorityId, Pair<Bundle, Long> targetPeriodicSync, long when) {
+ int authorityId, PeriodicSync targetPeriodicSync, long when) {
boolean found = false;
final AuthorityInfo authorityInfo;
synchronized (mAuthorities) {
authorityInfo = mAuthorities.get(authorityId);
for (int i = 0; i < authorityInfo.periodicSyncs.size(); i++) {
- Pair<Bundle, Long> periodicSync = authorityInfo.periodicSyncs.get(i);
- if (PeriodicSync.syncExtrasEquals(periodicSync.first, targetPeriodicSync.first)
- && periodicSync.second == targetPeriodicSync.second) {
+ PeriodicSync periodicSync = authorityInfo.periodicSyncs.get(i);
+ if (targetPeriodicSync.equals(periodicSync)) {
mSyncStatus.get(authorityId).setPeriodicSyncTime(i, when);
found = true;
break;
}
}
}
-
if (!found) {
Log.w(TAG, "Ignoring setPeriodicSyncTime request for a sync that does not exist. " +
"Authority: " + authorityInfo.authority);
@@ -1494,6 +1635,7 @@ public class SyncStorageEngine extends Handler {
synchronized (mAuthorities) {
mAuthorities.clear();
mAccounts.clear();
+ mServices.clear();
mPendingOperations.clear();
mSyncStatus.clear();
mSyncHistory.clear();
@@ -1555,7 +1697,7 @@ public class SyncStorageEngine extends Handler {
mMasterSyncAutomatically.put(0, listen == null || Boolean.parseBoolean(listen));
eventType = parser.next();
AuthorityInfo authority = null;
- Pair<Bundle, Long> periodicSync = null;
+ PeriodicSync periodicSync = null;
do {
if (eventType == XmlPullParser.START_TAG) {
tagName = parser.getName();
@@ -1575,7 +1717,7 @@ public class SyncStorageEngine extends Handler {
}
} else if (parser.getDepth() == 4 && periodicSync != null) {
if ("extra".equals(tagName)) {
- parseExtra(parser, periodicSync);
+ parseExtra(parser, periodicSync.extras);
}
}
}
@@ -1669,8 +1811,7 @@ public class SyncStorageEngine extends Handler {
AuthorityInfo authority = null;
int id = -1;
try {
- id = Integer.parseInt(parser.getAttributeValue(
- null, "id"));
+ id = Integer.parseInt(parser.getAttributeValue(null, "id"));
} catch (NumberFormatException e) {
Log.e(TAG, "error parsing the id of the authority", e);
} catch (NullPointerException e) {
@@ -1683,6 +1824,8 @@ public class SyncStorageEngine extends Handler {
String accountName = parser.getAttributeValue(null, "account");
String accountType = parser.getAttributeValue(null, "type");
String user = parser.getAttributeValue(null, XML_ATTR_USER);
+ String packageName = parser.getAttributeValue(null, "package");
+ String className = parser.getAttributeValue(null, "class");
int userId = user == null ? 0 : Integer.parseInt(user);
if (accountType == null) {
accountType = "com.google";
@@ -1695,12 +1838,19 @@ public class SyncStorageEngine extends Handler {
+ " enabled=" + enabled
+ " syncable=" + syncable);
if (authority == null) {
- if (DEBUG_FILE) Log.v(TAG, "Creating entry");
- authority = getOrCreateAuthorityLocked(
- new Account(accountName, accountType), userId, authorityName, id, false);
+ if (DEBUG_FILE) {
+ Log.v(TAG, "Creating entry");
+ }
+ if (accountName != null && accountType != null) {
+ authority = getOrCreateAuthorityLocked(
+ new Account(accountName, accountType), userId, authorityName, id, false);
+ } else {
+ authority = getOrCreateAuthorityLocked(
+ new ComponentName(packageName, className), userId, id, false);
+ }
// If the version is 0 then we are upgrading from a file format that did not
// know about periodic syncs. In that case don't clear the list since we
- // want the default, which is a daily periodioc sync.
+ // want the default, which is a daily periodic sync.
// Otherwise clear out this default list since we will populate it later with
// the periodic sync descriptions that are read from the configuration file.
if (version > 0) {
@@ -1722,14 +1872,18 @@ public class SyncStorageEngine extends Handler {
+ " syncable=" + syncable);
}
}
-
return authority;
}
- private Pair<Bundle, Long> parsePeriodicSync(XmlPullParser parser, AuthorityInfo authority) {
- Bundle extras = new Bundle();
+ /**
+ * Parse a periodic sync from accounts.xml. Sets the bundle to be empty.
+ */
+ private PeriodicSync parsePeriodicSync(XmlPullParser parser, AuthorityInfo authority) {
+ Bundle extras = new Bundle(); // Gets filled in later.
String periodValue = parser.getAttributeValue(null, "period");
+ String flexValue = parser.getAttributeValue(null, "flex");
final long period;
+ long flextime;
try {
period = Long.parseLong(periodValue);
} catch (NumberFormatException e) {
@@ -1739,14 +1893,24 @@ public class SyncStorageEngine extends Handler {
Log.e(TAG, "the period of a periodic sync is null", e);
return null;
}
- final Pair<Bundle, Long> periodicSync = Pair.create(extras, period);
+ try {
+ flextime = Long.parseLong(flexValue);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Error formatting value parsed for periodic sync flex: " + flexValue);
+ flextime = calculateDefaultFlexTime(period);
+ } catch (NullPointerException expected) {
+ flextime = calculateDefaultFlexTime(period);
+ Log.d(TAG, "No flex time specified for this sync, using a default. period: "
+ + period + " flex: " + flextime);
+ }
+ final PeriodicSync periodicSync =
+ new PeriodicSync(authority.account, authority.authority, extras,
+ period, flextime);
authority.periodicSyncs.add(periodicSync);
-
return periodicSync;
}
- private void parseExtra(XmlPullParser parser, Pair<Bundle, Long> periodicSync) {
- final Bundle extras = periodicSync.first;
+ private void parseExtra(XmlPullParser parser, Bundle extras) {
String name = parser.getAttributeValue(null, "name");
String type = parser.getAttributeValue(null, "type");
String value1 = parser.getAttributeValue(null, "value1");
@@ -1806,62 +1970,37 @@ public class SyncStorageEngine extends Handler {
}
final int N = mAuthorities.size();
- for (int i=0; i<N; i++) {
+ for (int i = 0; i < N; i++) {
AuthorityInfo authority = mAuthorities.valueAt(i);
out.startTag(null, "authority");
out.attribute(null, "id", Integer.toString(authority.ident));
- out.attribute(null, "account", authority.account.name);
out.attribute(null, XML_ATTR_USER, Integer.toString(authority.userId));
- out.attribute(null, "type", authority.account.type);
- out.attribute(null, "authority", authority.authority);
out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(authority.enabled));
+ if (authority.service == null) {
+ out.attribute(null, "account", authority.account.name);
+ out.attribute(null, "type", authority.account.type);
+ out.attribute(null, "authority", authority.authority);
+ } else {
+ out.attribute(null, "package", authority.service.getPackageName());
+ out.attribute(null, "class", authority.service.getClassName());
+ }
if (authority.syncable < 0) {
out.attribute(null, "syncable", "unknown");
} else {
out.attribute(null, "syncable", Boolean.toString(authority.syncable != 0));
}
- for (Pair<Bundle, Long> periodicSync : authority.periodicSyncs) {
+ for (PeriodicSync periodicSync : authority.periodicSyncs) {
out.startTag(null, "periodicSync");
- out.attribute(null, "period", Long.toString(periodicSync.second));
- final Bundle extras = periodicSync.first;
- for (String key : extras.keySet()) {
- out.startTag(null, "extra");
- out.attribute(null, "name", key);
- final Object value = extras.get(key);
- if (value instanceof Long) {
- out.attribute(null, "type", "long");
- out.attribute(null, "value1", value.toString());
- } else if (value instanceof Integer) {
- out.attribute(null, "type", "integer");
- out.attribute(null, "value1", value.toString());
- } else if (value instanceof Boolean) {
- out.attribute(null, "type", "boolean");
- out.attribute(null, "value1", value.toString());
- } else if (value instanceof Float) {
- out.attribute(null, "type", "float");
- out.attribute(null, "value1", value.toString());
- } else if (value instanceof Double) {
- out.attribute(null, "type", "double");
- out.attribute(null, "value1", value.toString());
- } else if (value instanceof String) {
- out.attribute(null, "type", "string");
- out.attribute(null, "value1", value.toString());
- } else if (value instanceof Account) {
- out.attribute(null, "type", "account");
- out.attribute(null, "value1", ((Account)value).name);
- out.attribute(null, "value2", ((Account)value).type);
- }
- out.endTag(null, "extra");
- }
+ out.attribute(null, "period", Long.toString(periodicSync.period));
+ out.attribute(null, "flex", Long.toString(periodicSync.flexTime));
+ final Bundle extras = periodicSync.extras;
+ extrasToXml(out, extras);
out.endTag(null, "periodicSync");
}
out.endTag(null, "authority");
}
-
out.endTag(null, "accounts");
-
out.endDocument();
-
mAccountInfoFile.finishWrite(fos);
} catch (java.io.IOException e1) {
Log.w(TAG, "Error writing accounts", e1);
@@ -2072,7 +2211,7 @@ public class SyncStorageEngine extends Handler {
}
}
- public static final int PENDING_OPERATION_VERSION = 3;
+ public static final int PENDING_OPERATION_VERSION = 4;
/**
* Read all pending operations back in to the initial engine state.
@@ -2080,128 +2219,162 @@ public class SyncStorageEngine extends Handler {
private void readPendingOperationsLocked() {
if (DEBUG_FILE) Log.v(TAG, "Reading " + mPendingFile.getBaseFile());
try {
- byte[] data = mPendingFile.readFully();
- Parcel in = Parcel.obtain();
- in.unmarshall(data, 0, data.length);
- in.setDataPosition(0);
- final int SIZE = in.dataSize();
- while (in.dataPosition() < SIZE) {
- int version = in.readInt();
- if (version != PENDING_OPERATION_VERSION && version != 1) {
- Log.w(TAG, "Unknown pending operation version "
- + version + "; dropping all ops");
- break;
- }
- int authorityId = in.readInt();
- int syncSource = in.readInt();
- byte[] flatExtras = in.createByteArray();
- boolean expedited;
- if (version == PENDING_OPERATION_VERSION) {
- expedited = in.readInt() != 0;
- } else {
- expedited = false;
- }
- int reason = in.readInt();
- AuthorityInfo authority = mAuthorities.get(authorityId);
- if (authority != null) {
- Bundle extras;
- if (flatExtras != null) {
- extras = unflattenBundle(flatExtras);
- } else {
- // if we are unable to parse the extras for whatever reason convert this
- // to a regular sync by creating an empty extras
- extras = new Bundle();
- }
- PendingOperation op = new PendingOperation(
- authority.account, authority.userId, reason, syncSource,
- authority.authority, extras, expedited);
- op.authorityId = authorityId;
- op.flatExtras = flatExtras;
- if (DEBUG_FILE) Log.v(TAG, "Adding pending op: account=" + op.account
- + " auth=" + op.authority
- + " src=" + op.syncSource
- + " reason=" + op.reason
- + " expedited=" + op.expedited
- + " extras=" + op.extras);
- mPendingOperations.add(op);
- }
+ readPendingAsXml();
+ } catch (XmlPullParserException e) {
+ Log.d(TAG, "Error parsing pending as xml, trying as parcel.");
+ try {
+ readPendingAsParcelled();
+ } catch (java.io.IOException e1) {
+ Log.i(TAG, "No initial pending operations");
}
- } catch (java.io.IOException e) {
- Log.i(TAG, "No initial pending operations");
- }
- }
-
- private void writePendingOperationLocked(PendingOperation op, Parcel out) {
- out.writeInt(PENDING_OPERATION_VERSION);
- out.writeInt(op.authorityId);
- out.writeInt(op.syncSource);
- if (op.flatExtras == null && op.extras != null) {
- op.flatExtras = flattenBundle(op.extras);
}
- out.writeByteArray(op.flatExtras);
- out.writeInt(op.expedited ? 1 : 0);
- out.writeInt(op.reason);
}
- /**
- * Write all currently pending ops to the pending ops file.
- */
- private void writePendingOperationsLocked() {
- final int N = mPendingOperations.size();
- FileOutputStream fos = null;
+ private void readPendingAsXml() throws XmlPullParserException {
+ FileInputStream fis = null;
try {
- if (N == 0) {
- if (DEBUG_FILE) Log.v(TAG, "Truncating " + mPendingFile.getBaseFile());
- mPendingFile.truncate();
- return;
+ Log.v(TAG, "is this thing on");
+ fis = mPendingFile.openRead();
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(fis, null);
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.START_TAG &&
+ eventType != XmlPullParser.END_DOCUMENT) {
+ eventType = parser.next();
+ Log.v(TAG, "go: " + eventType);
}
+ if (eventType == XmlPullParser.END_DOCUMENT) return;
- if (DEBUG_FILE) Log.v(TAG, "Writing new " + mPendingFile.getBaseFile());
- fos = mPendingFile.startWrite();
-
- Parcel out = Parcel.obtain();
- for (int i=0; i<N; i++) {
- PendingOperation op = mPendingOperations.get(i);
- writePendingOperationLocked(op, out);
+ String tagName = parser.getName();
+ if (DEBUG_FILE) {
+ Log.v(TAG, "got " + tagName);
}
- fos.write(out.marshall());
- out.recycle();
-
- mPendingFile.finishWrite(fos);
- } catch (java.io.IOException e1) {
- Log.w(TAG, "Error writing pending operations", e1);
- if (fos != null) {
- mPendingFile.failWrite(fos);
+ if ("pending".equals(tagName)) {
+ int version = -1;
+ String versionString = parser.getAttributeValue(null, "version");
+ if (versionString == null ||
+ Integer.parseInt(versionString) != PENDING_OPERATION_VERSION) {
+ Log.w(TAG, "Unknown pending operation version "
+ + version + "; trying to read as binary.");
+ throw new XmlPullParserException("Unknown version.");
+ }
+ eventType = parser.next();
+ PendingOperation pop = null;
+ do {
+ if (DEBUG_FILE) {
+ Log.v(TAG, "parsing xml file");
+ }
+ if (eventType == XmlPullParser.START_TAG) {
+ try {
+ tagName = parser.getName();
+ if (parser.getDepth() == 2 && "op".equals(tagName)) {
+ int authorityId = Integer.valueOf(parser.getAttributeValue(
+ null, XML_ATTR_AUTHORITYID));
+ boolean expedited = Boolean.valueOf(parser.getAttributeValue(
+ null, XML_ATTR_EXPEDITED));
+ int syncSource = Integer.valueOf(parser.getAttributeValue(
+ null, XML_ATTR_SOURCE));
+ int reason = Integer.valueOf(parser.getAttributeValue(
+ null, XML_ATTR_REASON));
+ AuthorityInfo authority = mAuthorities.get(authorityId);
+ if (DEBUG_FILE) {
+ Log.v(TAG, authorityId + " " + expedited + " " + syncSource + " " + reason);
+ }
+ if (authority != null) {
+ pop = new PendingOperation(
+ authority.account, authority.userId, reason, syncSource,
+ authority.authority, new Bundle(), expedited);
+ pop.authorityId = authorityId;
+ pop.flatExtras = null; // No longer used.
+ mPendingOperations.add(pop);
+ if (DEBUG_FILE) Log.v(TAG, "Adding pending op: account=" + pop.account
+ + " auth=" + pop.authority
+ + " src=" + pop.syncSource
+ + " reason=" + pop.reason
+ + " expedited=" + pop.expedited);
+ } else {
+ // Skip non-existent authority;
+ pop = null;
+ if (DEBUG_FILE) {
+ Log.v(TAG, "No authority found for " + authorityId
+ + ", skipping");
+ }
+ }
+ } else if (parser.getDepth() == 3 &&
+ pop != null &&
+ "extra".equals(tagName)) {
+ parseExtra(parser, pop.extras);
+ }
+ } catch (NumberFormatException e) {
+ Log.d(TAG, "Invalid data in xml file.", e);
+ }
+ }
+ eventType = parser.next();
+ } while(eventType != XmlPullParser.END_DOCUMENT);
+ }
+ } catch (java.io.IOException e) {
+ if (fis == null) Log.i(TAG, "No initial pending operations.");
+ else Log.w(TAG, "Error reading pending data.", e);
+ return;
+ } finally {
+ if (DEBUG_FILE) Log.v(TAG, "Done reading pending ops");
+ if (fis != null) {
+ try {
+ fis.close();
+ } catch (java.io.IOException e1) {}
}
}
}
-
/**
- * Append the given operation to the pending ops file; if unable to,
- * write all pending ops.
+ * Old format of reading pending.bin as a parcelled file. Replaced in lieu of JSON because
+ * persisting parcels is unsafe.
+ * @throws java.io.IOException
*/
- private void appendPendingOperationLocked(PendingOperation op) {
- if (DEBUG_FILE) Log.v(TAG, "Appending to " + mPendingFile.getBaseFile());
- FileOutputStream fos = null;
- try {
- fos = mPendingFile.openAppend();
- } catch (java.io.IOException e) {
- if (DEBUG_FILE) Log.v(TAG, "Failed append; writing full file");
- writePendingOperationsLocked();
- return;
- }
-
- try {
- Parcel out = Parcel.obtain();
- writePendingOperationLocked(op, out);
- fos.write(out.marshall());
- out.recycle();
- } catch (java.io.IOException e1) {
- Log.w(TAG, "Error writing pending operations", e1);
- } finally {
- try {
- fos.close();
- } catch (java.io.IOException e2) {
+ private void readPendingAsParcelled() throws java.io.IOException {
+ byte[] data = mPendingFile.readFully();
+ Parcel in = Parcel.obtain();
+ in.unmarshall(data, 0, data.length);
+ in.setDataPosition(0);
+ final int SIZE = in.dataSize();
+ while (in.dataPosition() < SIZE) {
+ int version = in.readInt();
+ if (version != 3 && version != 1) {
+ Log.w(TAG, "Unknown pending operation version "
+ + version + "; dropping all ops");
+ break;
+ }
+ int authorityId = in.readInt();
+ int syncSource = in.readInt();
+ byte[] flatExtras = in.createByteArray();
+ boolean expedited;
+ if (version == PENDING_OPERATION_VERSION) {
+ expedited = in.readInt() != 0;
+ } else {
+ expedited = false;
+ }
+ int reason = in.readInt();
+ AuthorityInfo authority = mAuthorities.get(authorityId);
+ if (authority != null) {
+ Bundle extras;
+ if (flatExtras != null) {
+ extras = unflattenBundle(flatExtras);
+ } else {
+ // if we are unable to parse the extras for whatever reason convert this
+ // to a regular sync by creating an empty extras
+ extras = new Bundle();
+ }
+ PendingOperation op = new PendingOperation(
+ authority.account, authority.userId, reason, syncSource,
+ authority.authority, extras, expedited);
+ op.authorityId = authorityId;
+ op.flatExtras = flatExtras;
+ if (DEBUG_FILE) Log.v(TAG, "Adding pending op: account=" + op.account
+ + " auth=" + op.authority
+ + " src=" + op.syncSource
+ + " reason=" + op.reason
+ + " expedited=" + op.expedited
+ + " extras=" + op.extras);
+ mPendingOperations.add(op);
}
}
}
@@ -2235,6 +2408,115 @@ public class SyncStorageEngine extends Handler {
return bundle;
}
+ private static final String XML_ATTR_AUTHORITYID = "authority_id";
+ private static final String XML_ATTR_SOURCE = "source";
+ private static final String XML_ATTR_EXPEDITED = "expedited";
+ private static final String XML_ATTR_REASON = "reason";
+ /**
+ * Write all currently pending ops to the pending ops file. TODO: Change this from xml
+ * so that we can append to this file as before.
+ */
+ private void writePendingOperationsLocked() {
+ final int N = mPendingOperations.size();
+ FileOutputStream fos = null;
+ try {
+ if (N == 0) {
+ if (DEBUG_FILE) Log.v(TAG, "Truncating " + mPendingFile.getBaseFile());
+ mPendingFile.truncate();
+ return;
+ }
+ if (DEBUG_FILE) Log.v(TAG, "Writing new " + mPendingFile.getBaseFile());
+ fos = mPendingFile.startWrite();
+ XmlSerializer out = new FastXmlSerializer();
+ out.setOutput(fos, "utf-8");
+ out.startDocument(null, true);
+ out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+
+ out.startTag(null, "pending");
+ out.attribute(null, "version", Integer.toString(PENDING_OPERATION_VERSION));
+
+ for (int i = 0; i < N; i++) {
+ PendingOperation pop = mPendingOperations.get(i);
+ out.startTag(null, "op");
+ out.attribute(null, XML_ATTR_AUTHORITYID, Integer.toString(pop.authorityId));
+ out.attribute(null, XML_ATTR_SOURCE, Integer.toString(pop.syncSource));
+ out.attribute(null, XML_ATTR_EXPEDITED, Boolean.toString(pop.expedited));
+ out.attribute(null, XML_ATTR_REASON, Integer.toString(pop.reason));
+ extrasToXml(out, pop.extras);
+ out.endTag(null, "op");
+ }
+ out.endTag(null, "pending");
+ out.endDocument();
+ mPendingFile.finishWrite(fos);
+ } catch (java.io.IOException e1) {
+ Log.w(TAG, "Error writing pending operations", e1);
+ if (fos != null) {
+ mPendingFile.failWrite(fos);
+ }
+ }
+ }
+
+ private void extrasToXml(XmlSerializer out, Bundle extras) throws java.io.IOException {
+ for (String key : extras.keySet()) {
+ out.startTag(null, "extra");
+ out.attribute(null, "name", key);
+ final Object value = extras.get(key);
+ if (value instanceof Long) {
+ out.attribute(null, "type", "long");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Integer) {
+ out.attribute(null, "type", "integer");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Boolean) {
+ out.attribute(null, "type", "boolean");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Float) {
+ out.attribute(null, "type", "float");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Double) {
+ out.attribute(null, "type", "double");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof String) {
+ out.attribute(null, "type", "string");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Account) {
+ out.attribute(null, "type", "account");
+ out.attribute(null, "value1", ((Account)value).name);
+ out.attribute(null, "value2", ((Account)value).type);
+ }
+ out.endTag(null, "extra");
+ }
+ }
+
+// /**
+// * Update the pending ops file, if e
+// */
+// private void appendPendingOperationLocked(PendingOperation op) {
+// if (DEBUG_FILE) Log.v(TAG, "Appending to " + mPendingFile.getBaseFile());
+// FileOutputStream fos = null;
+// try {
+// fos = mPendingFile.openAppend();
+// } catch (java.io.IOException e) {
+// if (DEBUG_FILE) Log.v(TAG, "Failed append; writing full file");
+// writePendingOperationsLocked();
+// return;
+// }
+//
+// try {
+// Parcel out = Parcel.obtain();
+// writePendingOperationLocked(op, out);
+// fos.write(out.marshall());
+// out.recycle();
+// } catch (java.io.IOException e1) {
+// Log.w(TAG, "Error writing pending operations", e1);
+// } finally {
+// try {
+// fos.close();
+// } catch (java.io.IOException e2) {
+// }
+// }
+// }
+
private void requestSync(Account account, int userId, int reason, String authority,
Bundle extras) {
// If this is happening in the system process, then call the syncrequest listener
@@ -2330,4 +2612,18 @@ public class SyncStorageEngine extends Handler {
}
}
}
+
+ /**
+ * Dump state of PendingOperations.
+ */
+ public void dumpPendingOperations(StringBuilder sb) {
+ sb.append("Pending Ops: ").append(mPendingOperations.size()).append(" operation(s)\n");
+ for (PendingOperation pop : mPendingOperations) {
+ sb.append("(" + pop.account)
+ .append(", " + pop.userId)
+ .append(", " + pop.authority)
+ .append(", " + pop.extras)
+ .append(")\n");
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
index f2772c8..37176d6 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncOperationTest.java
@@ -14,9 +14,10 @@
* limitations under the License.
*/
-package com.android.server;
+package com.android.server.content;
import android.accounts.Account;
+import android.content.ContentResolver;
import android.os.Bundle;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
@@ -48,7 +49,8 @@ public class SyncOperationTest extends AndroidTestCase {
SyncOperation.REASON_PERIODIC,
"authority1",
b1,
- 100,
+ 100, /* run time from now*/
+ 10, /* flex */
1000,
10000,
false);
@@ -60,6 +62,7 @@ public class SyncOperationTest extends AndroidTestCase {
"authority1",
b1,
200,
+ 20,
2000,
20000,
false);
@@ -71,6 +74,7 @@ public class SyncOperationTest extends AndroidTestCase {
"authority2",
b1,
100,
+ 10,
1000,
10000,
false);
@@ -82,6 +86,7 @@ public class SyncOperationTest extends AndroidTestCase {
"authority1",
b1,
100,
+ 10,
1000,
10000,
false);
@@ -93,6 +98,7 @@ public class SyncOperationTest extends AndroidTestCase {
"authority1",
b2,
100,
+ 10,
1000,
10000,
false);
@@ -102,4 +108,38 @@ public class SyncOperationTest extends AndroidTestCase {
assertNotSame(op1.key, op4.key);
assertNotSame(op1.key, op5.key);
}
+
+ @SmallTest
+ public void testCompareTo() {
+ Account dummy = new Account("account1", "type1");
+ Bundle b1 = new Bundle();
+ final long unimportant = 0L;
+ long soon = 1000;
+ long soonFlex = 50;
+ long after = 1500;
+ long afterFlex = 100;
+ SyncOperation op1 = new SyncOperation(dummy, 0, 0, SyncOperation.REASON_PERIODIC,
+ "authority1", b1, soon, soonFlex, unimportant, unimportant, true);
+
+ // Interval disjoint from and after op1.
+ SyncOperation op2 = new SyncOperation(dummy, 0, 0, SyncOperation.REASON_PERIODIC,
+ "authority1", b1, after, afterFlex, unimportant, unimportant, true);
+
+ // Interval equivalent to op1, but expedited.
+ Bundle b2 = new Bundle();
+ b2.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
+ SyncOperation op3 = new SyncOperation(dummy, 0, 0, 0,
+ "authority1", b2, soon, soonFlex, unimportant, unimportant, true);
+
+ // Interval overlaps but not equivalent to op1.
+ SyncOperation op4 = new SyncOperation(dummy, 0, 0, SyncOperation.REASON_PERIODIC,
+ "authority1", b1, soon + 100, soonFlex + 100, unimportant, unimportant, true);
+
+ assertTrue(op1.compareTo(op2) == -1);
+ assertTrue("less than not transitive.", op2.compareTo(op1) == 1);
+ assertTrue(op1.compareTo(op3) == 1);
+ assertTrue("greater than not transitive. ", op3.compareTo(op1) == -1);
+ assertTrue("overlapping intervals not the same.", op1.compareTo(op4) == 0);
+ assertTrue("equality not transitive.", op4.compareTo(op1) == 0);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
index 8b00f2c..dff6661 100644
--- a/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
+++ b/services/tests/servicestests/src/com/android/server/content/SyncStorageEngineTest.java
@@ -22,6 +22,7 @@ import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.PeriodicSync;
+import android.content.res.Resources;
import android.os.Bundle;
import android.test.AndroidTestCase;
import android.test.RenamingDelegatingContext;
@@ -39,10 +40,31 @@ import java.util.List;
public class SyncStorageEngineTest extends AndroidTestCase {
+ protected Account account1;
+ protected String authority1 = "testprovider";
+ protected Bundle defaultBundle;
+ protected final int DEFAULT_USER = 0;
+
+ MockContentResolver mockResolver;
+ SyncStorageEngine engine;
+
private File getSyncDir() {
return new File(new File(getContext().getFilesDir(), "system"), "sync");
}
+ @Override
+ public void setUp() {
+ account1 = new Account("a@example.com", "example.type");
+ // Default bundle.
+ defaultBundle = new Bundle();
+ defaultBundle.putInt("int_key", 0);
+ defaultBundle.putString("string_key", "hello");
+ // Set up storage engine.
+ mockResolver = new MockContentResolver();
+ engine = SyncStorageEngine.newTestInstance(
+ new TestContext(mockResolver, getContext()));
+ }
+
/**
* Test that we handle the case of a history row being old enough to purge before the
* correcponding sync is finished. This can happen if the clock changes while we are syncing.
@@ -68,7 +90,25 @@ public class SyncStorageEngineTest extends AndroidTestCase {
}
/**
- * Test that we can create, remove and retrieve periodic syncs
+ * Test persistence of pending operations.
+ */
+ @MediumTest
+ public void testPending() throws Exception {
+ SyncStorageEngine.PendingOperation pop =
+ new SyncStorageEngine.PendingOperation(account1, DEFAULT_USER,
+ SyncOperation.REASON_PERIODIC, SyncStorageEngine.SOURCE_LOCAL,
+ authority1, defaultBundle, false);
+
+ engine.insertIntoPending(pop);
+ // Force engine to read from disk.
+ engine.clearAndReadState();
+
+ assert(engine.getPendingOperationCount() == 1);
+ }
+
+ /**
+ * Test that we can create, remove and retrieve periodic syncs. Backwards compatibility -
+ * periodic syncs with no flex time are no longer used.
*/
@MediumTest
public void testPeriodics() throws Exception {
@@ -87,6 +127,64 @@ public class SyncStorageEngineTest extends AndroidTestCase {
PeriodicSync sync3 = new PeriodicSync(account1, authority, extras2, period2);
PeriodicSync sync4 = new PeriodicSync(account2, authority, extras2, period2);
+
+
+ removePeriodicSyncs(engine, account1, 0, authority);
+ removePeriodicSyncs(engine, account2, 0, authority);
+ removePeriodicSyncs(engine, account1, 1, authority);
+
+ // this should add two distinct periodic syncs for account1 and one for account2
+ engine.addPeriodicSync(sync1, 0);
+ engine.addPeriodicSync(sync2, 0);
+ engine.addPeriodicSync(sync3, 0);
+ engine.addPeriodicSync(sync4, 0);
+ // add a second user
+ engine.addPeriodicSync(sync2, 1);
+
+ List<PeriodicSync> syncs = engine.getPeriodicSyncs(account1, 0, authority);
+
+ assertEquals(2, syncs.size());
+
+ assertEquals(sync1, syncs.get(0));
+ assertEquals(sync3, syncs.get(1));
+
+ engine.removePeriodicSync(sync1, 0);
+
+ syncs = engine.getPeriodicSyncs(account1, 0, authority);
+ assertEquals(1, syncs.size());
+ assertEquals(sync3, syncs.get(0));
+
+ syncs = engine.getPeriodicSyncs(account2, 0, authority);
+ assertEquals(1, syncs.size());
+ assertEquals(sync4, syncs.get(0));
+
+ syncs = engine.getPeriodicSyncs(sync2.account, 1, sync2.authority);
+ assertEquals(1, syncs.size());
+ assertEquals(sync2, syncs.get(0));
+ }
+
+ /**
+ * Test that we can create, remove and retrieve periodic syncs with a provided flex time.
+ */
+ @MediumTest
+ public void testPeriodicsV2() throws Exception {
+ final Account account1 = new Account("a@example.com", "example.type");
+ final Account account2 = new Account("b@example.com", "example.type.2");
+ final String authority = "testprovider";
+ final Bundle extras1 = new Bundle();
+ extras1.putString("a", "1");
+ final Bundle extras2 = new Bundle();
+ extras2.putString("a", "2");
+ final int period1 = 200;
+ final int period2 = 1000;
+ final int flex1 = 10;
+ final int flex2 = 100;
+
+ PeriodicSync sync1 = new PeriodicSync(account1, authority, extras1, period1, flex1);
+ PeriodicSync sync2 = new PeriodicSync(account1, authority, extras2, period1, flex1);
+ PeriodicSync sync3 = new PeriodicSync(account1, authority, extras2, period2, flex2);
+ PeriodicSync sync4 = new PeriodicSync(account2, authority, extras2, period2, flex2);
+
MockContentResolver mockResolver = new MockContentResolver();
SyncStorageEngine engine = SyncStorageEngine.newTestInstance(
@@ -96,13 +194,13 @@ public class SyncStorageEngineTest extends AndroidTestCase {
removePeriodicSyncs(engine, account2, 0, authority);
removePeriodicSyncs(engine, account1, 1, authority);
- // this should add two distinct periodic syncs for account1 and one for account2
- engine.addPeriodicSync(sync1.account, 0, sync1.authority, sync1.extras, sync1.period);
- engine.addPeriodicSync(sync2.account, 0, sync2.authority, sync2.extras, sync2.period);
- engine.addPeriodicSync(sync3.account, 0, sync3.authority, sync3.extras, sync3.period);
- engine.addPeriodicSync(sync4.account, 0, sync4.authority, sync4.extras, sync4.period);
+ // This should add two distinct periodic syncs for account1 and one for account2
+ engine.addPeriodicSync(sync1, 0);
+ engine.addPeriodicSync(sync2, 0);
+ engine.addPeriodicSync(sync3, 0); // Should edit sync2 and update the period.
+ engine.addPeriodicSync(sync4, 0);
// add a second user
- engine.addPeriodicSync(sync2.account, 1, sync2.authority, sync2.extras, sync2.period);
+ engine.addPeriodicSync(sync2, 1);
List<PeriodicSync> syncs = engine.getPeriodicSyncs(account1, 0, authority);
@@ -111,7 +209,7 @@ public class SyncStorageEngineTest extends AndroidTestCase {
assertEquals(sync1, syncs.get(0));
assertEquals(sync3, syncs.get(1));
- engine.removePeriodicSync(sync1.account, 0, sync1.authority, sync1.extras);
+ engine.removePeriodicSync(sync1, 0);
syncs = engine.getPeriodicSyncs(account1, 0, authority);
assertEquals(1, syncs.size());
@@ -126,13 +224,11 @@ public class SyncStorageEngineTest extends AndroidTestCase {
assertEquals(sync2, syncs.get(0));
}
- private void removePeriodicSyncs(SyncStorageEngine engine, Account account, int userId,
- String authority) {
- engine.setIsSyncable(account, userId, authority,
- engine.getIsSyncable(account, 0, authority));
+ private void removePeriodicSyncs(SyncStorageEngine engine, Account account, int userId, String authority) {
+ engine.setIsSyncable(account, userId, authority, engine.getIsSyncable(account, 0, authority));
List<PeriodicSync> syncs = engine.getPeriodicSyncs(account, userId, authority);
for (PeriodicSync sync : syncs) {
- engine.removePeriodicSync(sync.account, userId, sync.authority, sync.extras);
+ engine.removePeriodicSync(sync, userId);
}
}
@@ -154,12 +250,14 @@ public class SyncStorageEngineTest extends AndroidTestCase {
extras2.putParcelable("g", account1);
final int period1 = 200;
final int period2 = 1000;
+ final int flex1 = 10;
+ final int flex2 = 100;
- PeriodicSync sync1 = new PeriodicSync(account1, authority1, extras1, period1);
- PeriodicSync sync2 = new PeriodicSync(account1, authority1, extras2, period1);
- PeriodicSync sync3 = new PeriodicSync(account1, authority2, extras1, period1);
- PeriodicSync sync4 = new PeriodicSync(account1, authority2, extras2, period2);
- PeriodicSync sync5 = new PeriodicSync(account2, authority1, extras1, period1);
+ PeriodicSync sync1 = new PeriodicSync(account1, authority1, extras1, period1, flex1);
+ PeriodicSync sync2 = new PeriodicSync(account1, authority1, extras2, period1, flex1);
+ PeriodicSync sync3 = new PeriodicSync(account1, authority2, extras1, period1, flex1);
+ PeriodicSync sync4 = new PeriodicSync(account1, authority2, extras2, period2, flex2);
+ PeriodicSync sync5 = new PeriodicSync(account2, authority1, extras1, period1, flex1);
MockContentResolver mockResolver = new MockContentResolver();
@@ -185,11 +283,11 @@ public class SyncStorageEngineTest extends AndroidTestCase {
engine.setIsSyncable(account2, 0, authority2, 0);
engine.setSyncAutomatically(account2, 0, authority2, true);
- engine.addPeriodicSync(sync1.account, 0, sync1.authority, sync1.extras, sync1.period);
- engine.addPeriodicSync(sync2.account, 0, sync2.authority, sync2.extras, sync2.period);
- engine.addPeriodicSync(sync3.account, 0, sync3.authority, sync3.extras, sync3.period);
- engine.addPeriodicSync(sync4.account, 0, sync4.authority, sync4.extras, sync4.period);
- engine.addPeriodicSync(sync5.account, 0, sync5.authority, sync5.extras, sync5.period);
+ engine.addPeriodicSync(sync1, 0);
+ engine.addPeriodicSync(sync2, 0);
+ engine.addPeriodicSync(sync3, 0);
+ engine.addPeriodicSync(sync4, 0);
+ engine.addPeriodicSync(sync5, 0);
engine.writeAllState();
engine.clearAndReadState();
@@ -220,6 +318,131 @@ public class SyncStorageEngineTest extends AndroidTestCase {
}
@MediumTest
+ /**
+ * V2 introduces flex time as well as service components.
+ * @throws Exception
+ */
+ public void testAuthorityParsingV2() throws Exception {
+ final Account account = new Account("account1", "type1");
+ final String authority1 = "auth1";
+ final String authority2 = "auth2";
+ final String authority3 = "auth3";
+
+ final long dayPoll = (60 * 60 * 24);
+ final long dayFuzz = 60;
+ final long thousandSecs = 1000;
+ final long thousandSecsFuzz = 100;
+ final Bundle extras = new Bundle();
+ PeriodicSync sync1 = new PeriodicSync(account, authority1, extras, dayPoll, dayFuzz);
+ PeriodicSync sync2 = new PeriodicSync(account, authority2, extras, dayPoll, dayFuzz);
+ PeriodicSync sync3 = new PeriodicSync(account, authority3, extras, dayPoll, dayFuzz);
+ PeriodicSync sync1s = new PeriodicSync(account, authority1, extras, thousandSecs, thousandSecsFuzz);
+ PeriodicSync sync2s = new PeriodicSync(account, authority2, extras, thousandSecs, thousandSecsFuzz);
+ PeriodicSync sync3s = new PeriodicSync(account, authority3, extras, thousandSecs, thousandSecsFuzz);
+ MockContentResolver mockResolver = new MockContentResolver();
+
+ final TestContext testContext = new TestContext(mockResolver, getContext());
+
+ byte[] accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<accounts version=\"2\" >\n"
+ + "<authority id=\"0\" user=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\" >"
+ + "\n<periodicSync period=\"" + dayPoll + "\" flex=\"" + dayFuzz + "\"/>"
+ + "\n</authority>"
+ + "<authority id=\"1\" user=\"0\" account=\"account1\" type=\"type1\" authority=\"auth2\" >"
+ + "\n<periodicSync period=\"" + dayPoll + "\" flex=\"" + dayFuzz + "\"/>"
+ + "\n</authority>"
+ // No user defaults to user 0 - all users.
+ + "<authority id=\"2\" account=\"account1\" type=\"type1\" authority=\"auth3\" >"
+ + "\n<periodicSync period=\"" + dayPoll + "\" flex=\"" + dayFuzz + "\"/>"
+ + "\n</authority>"
+ + "<authority id=\"3\" user=\"1\" account=\"account1\" type=\"type1\" authority=\"auth3\" >"
+ + "\n<periodicSync period=\"" + dayPoll + "\" flex=\"" + dayFuzz + "\"/>"
+ + "\n</authority>"
+ + "</accounts>").getBytes();
+
+ File syncDir = getSyncDir();
+ syncDir.mkdirs();
+ AtomicFile accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
+ FileOutputStream fos = accountInfoFile.startWrite();
+ fos.write(accountsFileData);
+ accountInfoFile.finishWrite(fos);
+
+ SyncStorageEngine engine = SyncStorageEngine.newTestInstance(testContext);
+
+ List<PeriodicSync> syncs = engine.getPeriodicSyncs(account, 0, authority1);
+ assertEquals("Got incorrect # of syncs", 1, syncs.size());
+ assertEquals(sync1, syncs.get(0));
+
+ syncs = engine.getPeriodicSyncs(account, 0, authority2);
+ assertEquals(1, syncs.size());
+ assertEquals(sync2, syncs.get(0));
+
+ syncs = engine.getPeriodicSyncs(account, 0, authority3);
+ assertEquals(1, syncs.size());
+ assertEquals(sync3, syncs.get(0));
+
+ syncs = engine.getPeriodicSyncs(account, 1, authority3);
+ assertEquals(1, syncs.size());
+ assertEquals(sync3, syncs.get(0));
+
+ // Test empty periodic data.
+ accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<accounts version=\"2\">\n"
+ + "<authority id=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\" />\n"
+ + "<authority id=\"1\" account=\"account1\" type=\"type1\" authority=\"auth2\" />\n"
+ + "<authority id=\"2\" account=\"account1\" type=\"type1\" authority=\"auth3\" />\n"
+ + "</accounts>\n").getBytes();
+
+ accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
+ fos = accountInfoFile.startWrite();
+ fos.write(accountsFileData);
+ accountInfoFile.finishWrite(fos);
+
+ engine.clearAndReadState();
+
+ syncs = engine.getPeriodicSyncs(account, 0, authority1);
+ assertEquals(0, syncs.size());
+
+ syncs = engine.getPeriodicSyncs(account, 0, authority2);
+ assertEquals(0, syncs.size());
+
+ syncs = engine.getPeriodicSyncs(account, 0, authority3);
+ assertEquals(0, syncs.size());
+
+ accountsFileData = ("<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<accounts version=\"2\">\n"
+ + "<authority id=\"0\" account=\"account1\" type=\"type1\" authority=\"auth1\">\n"
+ + "<periodicSync period=\"1000\" />\n"
+ + "</authority>"
+ + "<authority id=\"1\" account=\"account1\" type=\"type1\" authority=\"auth2\">\n"
+ + "<periodicSync period=\"1000\" />\n"
+ + "</authority>"
+ + "<authority id=\"2\" account=\"account1\" type=\"type1\" authority=\"auth3\">\n"
+ + "<periodicSync period=\"1000\" />\n"
+ + "</authority>"
+ + "</accounts>\n").getBytes();
+
+ accountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
+ fos = accountInfoFile.startWrite();
+ fos.write(accountsFileData);
+ accountInfoFile.finishWrite(fos);
+
+ engine.clearAndReadState();
+
+ syncs = engine.getPeriodicSyncs(account, 0, authority1);
+ assertEquals(1, syncs.size());
+ assertEquals(sync1s, syncs.get(0));
+
+ syncs = engine.getPeriodicSyncs(account, 0, authority2);
+ assertEquals(1, syncs.size());
+ assertEquals(sync2s, syncs.get(0));
+
+ syncs = engine.getPeriodicSyncs(account, 0, authority3);
+ assertEquals(1, syncs.size());
+ assertEquals(sync3s, syncs.get(0));
+ }
+
+ @MediumTest
public void testAuthorityParsing() throws Exception {
final Account account = new Account("account1", "type1");
final String authority1 = "auth1";
@@ -256,7 +479,7 @@ public class SyncStorageEngineTest extends AndroidTestCase {
List<PeriodicSync> syncs = engine.getPeriodicSyncs(account, 0, authority1);
assertEquals(1, syncs.size());
- assertEquals(sync1, syncs.get(0));
+ assertEquals("expected sync1: " + sync1.toString() + " == sync 2" + syncs.get(0).toString(), sync1, syncs.get(0));
syncs = engine.getPeriodicSyncs(account, 0, authority2);
assertEquals(1, syncs.size());
@@ -451,6 +674,11 @@ class TestContext extends ContextWrapper {
}
@Override
+ public Resources getResources() {
+ return mRealContext.getResources();
+ }
+
+ @Override
public File getFilesDir() {
return mRealContext.getFilesDir();
}