summaryrefslogtreecommitdiffstats
path: root/services/java/com
diff options
context:
space:
mode:
Diffstat (limited to 'services/java/com')
-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
5 files changed, 1121 insertions, 356 deletions
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");
+ }
+ }
}