diff options
Diffstat (limited to 'services/java')
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"); + } + } } |