FileDocCategorySizeDatePackage
SyncManager.javaAPI DocAndroid 1.5 API94000Wed May 06 22:41:54 BST 2009android.content

SyncManager

public class SyncManager extends Object
hide

Fields Summary
private static final String
TAG
private static final long
MILLIS_IN_HOUR
private static final long
MILLIS_IN_DAY
private static final long
MILLIS_IN_WEEK
private static final long
MILLIS_IN_4WEEKS
private static final long
LOCAL_SYNC_DELAY
Delay a sync due to local changes this long. In milliseconds
private static final long
MAX_TIME_PER_SYNC
If a sync takes longer than this and the sync queue is not empty then we will cancel it and add it back to the end of the sync queue. In milliseconds.
private static final long
SYNC_NOTIFICATION_DELAY
private static final long
INITIAL_SYNC_RETRY_TIME_IN_MS
When retrying a sync for the first time use this delay. After that the retry time will double until it reached MAX_SYNC_RETRY_TIME. In milliseconds.
private static final long
DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS
Default the max sync retry time to this value.
private static final long
ERROR_NOTIFICATION_DELAY_MS
An error notification is sent if sync of any of the providers has been failing for this long.
private static final String
SYNC_WAKE_LOCK
private static final String
HANDLE_SYNC_ALARM_WAKE_LOCK
private Context
mContext
private ContentResolver
mContentResolver
private String
mStatusText
private long
mHeartbeatTime
private android.accounts.AccountMonitor
mAccountMonitor
private volatile String[]
mAccounts
private volatile PowerManager.WakeLock
mSyncWakeLock
private volatile PowerManager.WakeLock
mHandleAlarmWakeLock
private volatile boolean
mDataConnectionIsConnected
private volatile boolean
mStorageIsLow
private Sync.Settings.QueryMap
mSyncSettings
private final android.app.NotificationManager
mNotificationMgr
private android.app.AlarmManager
mAlarmService
private android.os.HandlerThread
mSyncThread
private volatile android.content.pm.IPackageManager
mPackageManager
private final SyncStorageEngine
mSyncStorageEngine
private final SyncQueue
mSyncQueue
private ActiveSyncContext
mActiveSyncContext
private boolean
mNeedSyncErrorNotification
private boolean
mNeedSyncActiveNotification
private volatile boolean
mSyncPollInitialized
private final android.app.PendingIntent
mSyncAlarmIntent
private final android.app.PendingIntent
mSyncPollAlarmIntent
private BroadcastReceiver
mStorageIntentReceiver
private BroadcastReceiver
mConnectivityIntentReceiver
private static final String
ACTION_SYNC_ALARM
private static final String
SYNC_POLL_ALARM
private final SyncHandler
mSyncHandler
private static final String[]
SYNC_ACTIVE_PROJECTION
private static final String[]
SYNC_PENDING_PROJECTION
private static final int
MAX_SYNC_POLL_DELAY_SECONDS
private static final int
MIN_SYNC_POLL_DELAY_SECONDS
private static final String
SYNCMANAGER_PREFS_FILENAME
private static String[]
STATUS_PROJECTION
Constructors Summary
public SyncManager(Context context, boolean factoryTest)


         
        // Initialize the SyncStorageEngine first, before registering observers
        // and creating threads and so on; it may fail if the disk is full.
        SyncStorageEngine.init(context);
        mSyncStorageEngine = SyncStorageEngine.getSingleton();
        mSyncQueue = new SyncQueue(mSyncStorageEngine);

        mContext = context;

        mSyncThread = new HandlerThread("SyncHandlerThread", Process.THREAD_PRIORITY_BACKGROUND);
        mSyncThread.start();
        mSyncHandler = new SyncHandler(mSyncThread.getLooper());

        mPackageManager = null;

        mSyncAlarmIntent = PendingIntent.getBroadcast(
                mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0);

        mSyncPollAlarmIntent = PendingIntent.getBroadcast(
                mContext, 0 /* ignored */, new Intent(SYNC_POLL_ALARM), 0);

        IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
        context.registerReceiver(mConnectivityIntentReceiver, intentFilter);

        intentFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
        intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
        context.registerReceiver(mStorageIntentReceiver, intentFilter);

        if (!factoryTest) {
            mNotificationMgr = (NotificationManager)
                context.getSystemService(Context.NOTIFICATION_SERVICE);
            context.registerReceiver(new SyncAlarmIntentReceiver(),
                    new IntentFilter(ACTION_SYNC_ALARM));
        } else {
            mNotificationMgr = null;
        }
        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        mSyncWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, SYNC_WAKE_LOCK);
        mSyncWakeLock.setReferenceCounted(false);

        // This WakeLock is used to ensure that we stay awake between the time that we receive
        // a sync alarm notification and when we finish processing it. We need to do this
        // because we don't do the work in the alarm handler, rather we do it in a message
        // handler.
        mHandleAlarmWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                HANDLE_SYNC_ALARM_WAKE_LOCK);
        mHandleAlarmWakeLock.setReferenceCounted(false);

        if (!factoryTest) {
            AccountMonitorListener listener = new AccountMonitorListener() {
                public void onAccountsUpdated(String[] accounts) {
                    final boolean hadAccountsAlready = mAccounts != null;
                    // copy the accounts into a new array and change mAccounts to point to it
                    String[] newAccounts = new String[accounts.length];
                    System.arraycopy(accounts, 0, newAccounts, 0, accounts.length);
                    mAccounts = newAccounts;

                    // if a sync is in progress yet it is no longer in the accounts list, cancel it
                    ActiveSyncContext activeSyncContext = mActiveSyncContext;
                    if (activeSyncContext != null) {
                        if (!ArrayUtils.contains(newAccounts,
                                activeSyncContext.mSyncOperation.account)) {
                            Log.d(TAG, "canceling sync since the account has been removed");
                            sendSyncFinishedOrCanceledMessage(activeSyncContext,
                                    null /* no result since this is a cancel */);
                        }
                    }

                    // we must do this since we don't bother scheduling alarms when
                    // the accounts are not set yet
                    sendCheckAlarmsMessage();

                    mSyncStorageEngine.doDatabaseCleanup(accounts);

                    if (hadAccountsAlready && mAccounts.length > 0) {
                        // request a sync so that if the password was changed we will retry any sync
                        // that failed when it was wrong
                        startSync(null /* all providers */, null /* no extras */);
                    }
                }
            };
            mAccountMonitor = new AccountMonitor(context, listener);
        }
    
Methods Summary
public voidcancelActiveSync(android.net.Uri uri)
Cancel the active sync if it matches the uri. The uri corresponds to the one passed in to startSync().

param
uri If non-null, the active sync is only canceled if it matches the uri. If null, any active sync is canceled.

        ActiveSyncContext activeSyncContext = mActiveSyncContext;
        if (activeSyncContext != null) {
            // if a Uri was specified then only cancel the sync if it matches the the uri
            if (uri != null) {
                if (!uri.getAuthority().equals(activeSyncContext.mSyncOperation.authority)) {
                    return;
                }
            }
            sendSyncFinishedOrCanceledMessage(activeSyncContext,
                    null /* no result since this is a cancel */);
        }
    
public voidclearScheduledSyncOperations(android.net.Uri uri)
Remove any scheduled sync operations that match uri. The uri corresponds to the one passed in to startSync().

param
uri If non-null, only operations that match the uri are cleared. If null, all operations are cleared.

        synchronized (mSyncQueue) {
            mSyncQueue.clear(null, uri != null ? uri.getAuthority() : null);
        }
    
protected voiddump(java.io.FileDescriptor fd, java.io.PrintWriter pw)

        StringBuilder sb = new StringBuilder();
        dumpSyncState(sb);
        sb.append("\n");
        if (isSyncEnabled()) {
            dumpSyncHistory(sb);
        }
        pw.println(sb.toString());
    
protected voiddumpSyncHistory(java.lang.StringBuilder sb)

        Cursor c = mSyncStorageEngine.query(Sync.History.CONTENT_URI, null, "event=?",
                new String[]{String.valueOf(Sync.History.EVENT_STOP)},
                Sync.HistoryColumns.EVENT_TIME + " desc");
        try {
            long numSyncsLastHour = 0, durationLastHour = 0;
            long numSyncsLastDay = 0, durationLastDay = 0;
            long numSyncsLastWeek = 0, durationLastWeek = 0;
            long numSyncsLast4Weeks = 0, durationLast4Weeks = 0;
            long numSyncsTotal = 0, durationTotal = 0;

            long now = System.currentTimeMillis();
            int indexEventTime = c.getColumnIndexOrThrow(Sync.History.EVENT_TIME);
            int indexElapsedTime = c.getColumnIndexOrThrow(Sync.History.ELAPSED_TIME);
            while (c.moveToNext()) {
                long duration = c.getLong(indexElapsedTime);
                long endTime = c.getLong(indexEventTime) + duration;
                long millisSinceStart = now - endTime;
                numSyncsTotal++;
                durationTotal += duration;
                if (millisSinceStart < MILLIS_IN_HOUR) {
                    numSyncsLastHour++;
                    durationLastHour += duration;
                }
                if (millisSinceStart < MILLIS_IN_DAY) {
                    numSyncsLastDay++;
                    durationLastDay += duration;
                }
                if (millisSinceStart < MILLIS_IN_WEEK) {
                    numSyncsLastWeek++;
                    durationLastWeek += duration;
                }
                if (millisSinceStart < MILLIS_IN_4WEEKS) {
                    numSyncsLast4Weeks++;
                    durationLast4Weeks += duration;
                }
            }
            dumpSyncIntervalHeader(sb);
            dumpSyncInterval(sb, "hour", MILLIS_IN_HOUR, numSyncsLastHour, durationLastHour);
            dumpSyncInterval(sb, "day", MILLIS_IN_DAY, numSyncsLastDay, durationLastDay);
            dumpSyncInterval(sb, "week", MILLIS_IN_WEEK, numSyncsLastWeek, durationLastWeek);
            dumpSyncInterval(sb, "4 weeks",
                    MILLIS_IN_4WEEKS, numSyncsLast4Weeks, durationLast4Weeks);
            dumpSyncInterval(sb, "total", 0, numSyncsTotal, durationTotal);
            dumpSyncIntervalFooter(sb);
        } finally {
            c.close();
        }
    
private voiddumpSyncHistoryFooter(java.lang.StringBuilder sb)

        sb.append(" |___________________________________________________________________________________________________________________________|\n");
    
private voiddumpSyncHistoryHeader(java.lang.StringBuilder sb, java.lang.String account)

        sb.append(" Account: ").append(account).append("\n");
        sb.append("  ___________________________________________________________________________________________________________________________\n");
        sb.append(" |                 |             num times synced           |   total  |         last success          |                     |\n");
        sb.append(" | authority       | local |  poll | server |  user | total | duration |  source |               time  |   result if failing |\n");
    
private voiddumpSyncHistoryRow(java.lang.StringBuilder sb, android.database.Cursor c)


          
        boolean hasSuccess = !c.isNull(9);
        boolean hasFailure = !c.isNull(11);
        Time timeSuccess = new Time();
        if (hasSuccess) timeSuccess.set(c.getLong(9));
        Time timeFailure = new Time();
        if (hasFailure) timeFailure.set(c.getLong(11));
        sb.append(String.format(" | %-15s | %5d | %5d | %6d | %5d | %5d | %8s | %7s | %19s | %19s |\n",
                c.getString(1),
                c.getLong(4),
                c.getLong(5),
                c.getLong(6),
                c.getLong(7),
                c.getLong(2),
                DateUtils.formatElapsedTime(c.getLong(3)/1000),
                hasSuccess ? Sync.History.SOURCES[c.getInt(8)] : "",
                hasSuccess ? timeSuccess.format("%Y-%m-%d %H:%M:%S") : "",
                hasFailure ? History.mesgToString(c.getString(12)) : ""));
    
private voiddumpSyncInterval(java.lang.StringBuilder sb, java.lang.String label, long interval, long numSyncs, long duration)

        sb.append(String.format(" | %-8s | %6d | %8.1f | %8.1f",
                label, numSyncs, ((float)duration/numSyncs)/1000, (float)duration/1000));
        if (interval > 0) {
            sb.append(String.format(" | %13.2f |\n", ((float)duration/interval)*100.0));
        } else {
            sb.append(String.format(" | %13s |\n", "na"));
        }
    
private voiddumpSyncIntervalFooter(java.lang.StringBuilder sb)

        sb.append(" |_________________________________________________________|\n");
    
private voiddumpSyncIntervalHeader(java.lang.StringBuilder sb)

        sb.append("Sync Stats\n");
        sb.append(" ___________________________________________________________\n");
        sb.append(" |          |        |   duration in sec   |               |\n");
        sb.append(" | interval |  count |  average |    total | % of interval |\n");
    
private voiddumpSyncPendingFooter(java.lang.StringBuilder sb)

        sb.append(" |__________________________________________________|\n");
    
private voiddumpSyncPendingHeader(java.lang.StringBuilder sb)

        sb.append(" ____________________________________________________\n");
        sb.append(" | account                        | authority       |\n");
    
private voiddumpSyncPendingRow(java.lang.StringBuilder sb, android.database.Cursor c)

        sb.append(String.format(" | %-30s | %-15s |\n", c.getString(0), c.getString(1)));
    
protected voiddumpSyncState(java.lang.StringBuilder sb)

        sb.append("sync enabled: ").append(isSyncEnabled()).append("\n");
        sb.append("data connected: ").append(mDataConnectionIsConnected).append("\n");
        sb.append("memory low: ").append(mStorageIsLow).append("\n");

        final String[] accounts = mAccounts;
        sb.append("accounts: ");
        if (accounts != null) {
            sb.append(accounts.length);
        } else {
            sb.append("none");
        }
        sb.append("\n");
        final long now = SystemClock.elapsedRealtime();
        sb.append("now: ").append(now).append("\n");
        sb.append("uptime: ").append(DateUtils.formatElapsedTime(now/1000)).append(" (HH:MM:SS)\n");
        sb.append("time spent syncing : ")
                .append(DateUtils.formatElapsedTime(
                        mSyncHandler.mSyncTimeTracker.timeSpentSyncing() / 1000))
                .append(" (HH:MM:SS), sync ")
                .append(mSyncHandler.mSyncTimeTracker.mLastWasSyncing ? "" : "not ")
                .append("in progress").append("\n");
        if (mSyncHandler.mAlarmScheduleTime != null) {
            sb.append("next alarm time: ").append(mSyncHandler.mAlarmScheduleTime)
                    .append(" (")
                    .append(DateUtils.formatElapsedTime((mSyncHandler.mAlarmScheduleTime-now)/1000))
                    .append(" (HH:MM:SS) from now)\n");
        } else {
            sb.append("no alarm is scheduled (there had better not be any pending syncs)\n");
        }

        sb.append("active sync: ").append(mActiveSyncContext).append("\n");

        sb.append("notification info: ");
        mSyncHandler.mSyncNotificationInfo.toString(sb);
        sb.append("\n");

        synchronized (mSyncQueue) {
            sb.append("sync queue: ");
            mSyncQueue.dump(sb);
        }

        Cursor c = mSyncStorageEngine.query(Sync.Active.CONTENT_URI,
                SYNC_ACTIVE_PROJECTION, null, null, null);
        sb.append("\n");
        try {
            if (c.moveToNext()) {
                final long durationInSeconds = (now - c.getLong(2)) / 1000;
                sb.append("Active sync: ").append(c.getString(0))
                        .append(" ").append(c.getString(1))
                        .append(", duration is ")
                        .append(DateUtils.formatElapsedTime(durationInSeconds)).append(".\n");
            } else {
                sb.append("No sync is in progress.\n");
            }
        } finally {
            c.close();
        }

        c = mSyncStorageEngine.query(Sync.Pending.CONTENT_URI,
                SYNC_PENDING_PROJECTION, null, null, "account, authority");
        sb.append("\nPending Syncs\n");
        try {
            if (c.getCount() != 0) {
                dumpSyncPendingHeader(sb);
                while (c.moveToNext()) {
                    dumpSyncPendingRow(sb, c);
                }
                dumpSyncPendingFooter(sb);
            } else {
                sb.append("none\n");
            }
        } finally {
            c.close();
        }

        String currentAccount = null;
        c = mSyncStorageEngine.query(Sync.Status.CONTENT_URI,
                STATUS_PROJECTION, null, null, "account, authority");
        sb.append("\nSync history by account and authority\n");
        try {
            while (c.moveToNext()) {
                if (!TextUtils.equals(currentAccount, c.getString(0))) {
                    if (currentAccount != null) {
                        dumpSyncHistoryFooter(sb);
                    }
                    currentAccount = c.getString(0);
                    dumpSyncHistoryHeader(sb, currentAccount);
                }

                dumpSyncHistoryRow(sb, c);
            }
            if (c.getCount() > 0) dumpSyncHistoryFooter(sb);
        } finally {
            c.close();
        }
    
private voidensureAlarmService()

        if (mAlarmService == null) {
            mAlarmService = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
        }
    
private voidensureContentResolver()

        if (mContentResolver == null) {
            mContentResolver = mContext.getContentResolver();
        }
    
public android.content.SyncManager$ActiveSyncContextgetActiveSyncContext()

        return mActiveSyncContext;
    
private android.content.pm.IPackageManagergetPackageManager()

        // Don't bother synchronizing on this. The worst that can happen is that two threads
        // can try to get the package manager at the same time but only one result gets
        // used. Since there is only one package manager in the system this doesn't matter.
        if (mPackageManager == null) {
            IBinder b = ServiceManager.getService("package");
            mPackageManager = IPackageManager.Stub.asInterface(b);
        }
        return mPackageManager;
    
private Sync.Settings.QueryMapgetSyncSettings()

        if (mSyncSettings == null) {
            mSyncSettings = new Sync.Settings.QueryMap(mContext.getContentResolver(), true,
                    new Handler());
            mSyncSettings.addObserver(new Observer(){
                public void update(Observable o, Object arg) {
                    // force the sync loop to run if the settings change
                    sendCheckAlarmsMessage();
                }
            });
        }
        return mSyncSettings;
    
public java.lang.StringgetSyncingAccount()

        ActiveSyncContext activeSyncContext = mActiveSyncContext;
        return (activeSyncContext != null) ? activeSyncContext.mSyncOperation.account : null;
    
private voidhandleSyncPollAlarm()

        // determine the next poll time
        long delayMs = jitterize(MIN_SYNC_POLL_DELAY_SECONDS, MAX_SYNC_POLL_DELAY_SECONDS) * 1000;
        long nextRelativePollTimeMs = SystemClock.elapsedRealtime() + delayMs;

        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "handleSyncPollAlarm: delay " + delayMs);

        // write the absolute time to shared preferences
        writeSyncPollTime(System.currentTimeMillis() + delayMs);

        // schedule an alarm for the next poll time
        scheduleSyncPollAlarm(nextRelativePollTimeMs);

        // perform a poll
        scheduleSync(null /* sync all syncable providers */, new Bundle(), 0 /* no delay */);
    
private synchronized voidinitializeSyncPoll()

        if (mSyncPollInitialized) return;
        mSyncPollInitialized = true;

        mContext.registerReceiver(new SyncPollAlarmReceiver(), new IntentFilter(SYNC_POLL_ALARM));

        // load the next poll time from shared preferences
        long absoluteAlarmTime = readSyncPollTime();

        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "initializeSyncPoll: absoluteAlarmTime is " + absoluteAlarmTime);
        }

        // Convert absoluteAlarmTime to elapsed realtime. If this time was in the past then
        // schedule the poll immediately, if it is too far in the future then cap it at
        // MAX_SYNC_POLL_DELAY_SECONDS.
        long absoluteNow = System.currentTimeMillis();
        long relativeNow = SystemClock.elapsedRealtime();
        long relativeAlarmTime = relativeNow;
        if (absoluteAlarmTime > absoluteNow) {
            long delayInMs = absoluteAlarmTime - absoluteNow;
            final int maxDelayInMs = MAX_SYNC_POLL_DELAY_SECONDS * 1000;
            if (delayInMs > maxDelayInMs) {
                delayInMs = MAX_SYNC_POLL_DELAY_SECONDS * 1000;
            }
            relativeAlarmTime += delayInMs;
        }

        // schedule an alarm for the next poll time
        scheduleSyncPollAlarm(relativeAlarmTime);
    
private booleanisSyncEnabled()
Returns whether or not sync is enabled. Sync can be enabled by setting the system property "ro.config.sync" to the value "yes". This is normally done at boot time on builds that support sync.

return
true if sync is enabled

        // Require the precise value "yes" to discourage accidental activation.
        return "yes".equals(SystemProperties.get("ro.config.sync"));
    
private longjitterize(long minValue, long maxValue)
Return a random value v that satisfies minValue <= v < maxValue. The difference between maxValue and minValue must be less than Integer.MAX_VALUE.

        Random random = new Random(SystemClock.elapsedRealtime());
        long spread = maxValue - minValue;
        if (spread > Integer.MAX_VALUE) {
            throw new IllegalArgumentException("the difference between the maxValue and the "
                    + "minValue must be less than " + Integer.MAX_VALUE);
        }
        return minValue + random.nextInt((int)spread);
    
voidmaybeRescheduleSync(SyncResult syncResult, android.content.SyncManager$SyncOperation previousSyncOperation)

        boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG);
        if (isLoggable) {
            Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", "
                    + previousSyncOperation);
        }

        // If the operation succeeded to some extent then retry immediately.
        // If this was a two-way sync then retry soft errors with an exponential backoff.
        // If this was an upward sync then schedule a two-way sync immediately.
        // Otherwise do not reschedule.

        if (syncResult.madeSomeProgress()) {
            if (isLoggable) {
                Log.d(TAG, "retrying sync operation immediately because "
                        + "even though it had an error it achieved some success");
            }
            rescheduleImmediately(previousSyncOperation);
        } else if (previousSyncOperation.extras.getBoolean(
                ContentResolver.SYNC_EXTRAS_UPLOAD, false)) {
            final SyncOperation newSyncOperation = new SyncOperation(previousSyncOperation);
            newSyncOperation.extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);
            newSyncOperation.setDelay(0);
            if (Config.LOGD) {
                Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
                        + "encountered an error: " + previousSyncOperation);
            }
            scheduleSyncOperation(newSyncOperation);
        } else if (syncResult.hasSoftError()) {
            long delay = rescheduleWithDelay(previousSyncOperation);
            if (delay >= 0) {
                if (isLoggable) {
                    Log.d(TAG, "retrying sync operation in " + delay + " ms because "
                            + "it encountered a soft error: " + previousSyncOperation);
                }
            }
        } else {
            if (Config.LOGD) {
                Log.d(TAG, "not retrying sync operation because the error is a hard error: "
                        + previousSyncOperation);
            }
        }
    
private voidpopulateProvidersList(android.net.Uri url, java.util.List names, java.util.List providers)

        try {
            final IPackageManager packageManager = getPackageManager();
            if (url == null) {
                packageManager.querySyncProviders(names, providers);
            } else {
                final String authority = url.getAuthority();
                ProviderInfo info = packageManager.resolveContentProvider(url.getAuthority(), 0);
                if (info != null) {
                    // only set this provider if the requested authority is the primary authority
                    String[] providerNames = info.authority.split(";");
                    if (url.getAuthority().equals(providerNames[0])) {
                        names.add(authority);
                        providers.add(info);
                    }
                }
            }
        } catch (RemoteException ex) {
            // we should really never get this, but if we do then clear the lists, which
            // will result in the dropping of the sync request
            Log.e(TAG, "error trying to get the ProviderInfo for " + url, ex);
            names.clear();
            providers.clear();
        }
    
private longreadSyncPollTime()

        File f = new File(SYNCMANAGER_PREFS_FILENAME);

        DataInputStream str = null;
        try {
            str = new DataInputStream(new FileInputStream(f));
            return str.readLong();
        } catch (FileNotFoundException e) {
            writeSyncPollTime(0);
        } catch (IOException e) {
            Log.w(TAG, "error reading file " + f, e);
        } finally {
            if (str != null) {
                try {
                    str.close();
                } catch (IOException e) {
                    Log.w(TAG, "error closing file " + f, e);
                }
            }
        }
        return 0;
    
private voidrescheduleImmediately(android.content.SyncManager$SyncOperation syncOperation)

        SyncOperation rescheduledSyncOperation = new SyncOperation(syncOperation);
        rescheduledSyncOperation.setDelay(0);
        scheduleSyncOperation(rescheduledSyncOperation);
    
private longrescheduleWithDelay(android.content.SyncManager$SyncOperation syncOperation)

        long newDelayInMs;

        if (syncOperation.delay == 0) {
            // The initial delay is the jitterized INITIAL_SYNC_RETRY_TIME_IN_MS
            newDelayInMs = jitterize(INITIAL_SYNC_RETRY_TIME_IN_MS,
                    (long)(INITIAL_SYNC_RETRY_TIME_IN_MS * 1.1));
        } else {
            // Subsequent delays are the double of the previous delay
            newDelayInMs = syncOperation.delay * 2;
        }

        // Cap the delay
        ensureContentResolver();
        long maxSyncRetryTimeInSeconds = Settings.Gservices.getLong(mContentResolver,
                Settings.Gservices.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
                DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS);
        if (newDelayInMs > maxSyncRetryTimeInSeconds * 1000) {
            newDelayInMs = maxSyncRetryTimeInSeconds * 1000;
        }

        SyncOperation rescheduledSyncOperation = new SyncOperation(syncOperation);
        rescheduledSyncOperation.setDelay(newDelayInMs);
        scheduleSyncOperation(rescheduledSyncOperation);
        return newDelayInMs;
    
public voidscheduleLocalSync(android.net.Uri url)

        final Bundle extras = new Bundle();
        extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
        scheduleSync(url, extras, LOCAL_SYNC_DELAY);
    
public voidscheduleSync(android.net.Uri url, android.os.Bundle extras, long delay)
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, set onlyTicklable to true), or a specific provider (set url to the content url of the provider).

If the ContentResolver.SYNC_EXTRAS_UPLOAD boolean in extras is true then initiate a sync that just checks for local changes to send to the server, otherwise initiate a sync that first gets any changes from the server before sending local changes back to the server.

If a specific provider is being synced (the url is non-null) then the extras can contain SyncAdapter-specific information to control what gets synced (e.g. which specific feed to sync).

You'll start getting callbacks after this.

param
url The Uri of a specific provider to be synced, or null to sync all providers.
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 sync. -1 means to make this the next sync to perform.

        boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
        if (isLoggable) {
            Log.v(TAG, "scheduleSync:"
                    + " delay " + delay
                    + ", url " + ((url == null) ? "(null)" : url)
                    + ", extras " + ((extras == null) ? "(null)" : extras));
        }

        if (!isSyncEnabled()) {
            if (isLoggable) {
                Log.v(TAG, "not syncing because sync is disabled");
            }
            setStatusText("Sync is disabled.");
            return;
        }

        if (mAccounts == null) setStatusText("The accounts aren't known yet.");
        if (!mDataConnectionIsConnected) setStatusText("No data connection");
        if (mStorageIsLow) setStatusText("Memory low");

        if (extras == null) extras = new Bundle();

        Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
        if (expedited) {
            delay = -1; // this means schedule at the front of the queue
        }

        String[] accounts;
        String accountFromExtras = extras.getString(ContentResolver.SYNC_EXTRAS_ACCOUNT);
        if (!TextUtils.isEmpty(accountFromExtras)) {
            accounts = new String[]{accountFromExtras};
        } else {
            // if the accounts aren't configured yet then we can't support an account-less
            // sync request
            accounts = mAccounts;
            if (accounts == null) {
                // not ready yet
                if (isLoggable) {
                    Log.v(TAG, "scheduleSync: no accounts yet, dropping");
                }
                return;
            }
            if (accounts.length == 0) {
                if (isLoggable) {
                    Log.v(TAG, "scheduleSync: no accounts configured, dropping");
                }
                setStatusText("No accounts are configured.");
                return;
            }
        }

        final boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);
        final boolean force = extras.getBoolean(ContentResolver.SYNC_EXTRAS_FORCE, false);

        int source;
        if (uploadOnly) {
            source = Sync.History.SOURCE_LOCAL;
        } else if (force) {
            source = Sync.History.SOURCE_USER;
        } else if (url == null) {
            source = Sync.History.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 = Sync.History.SOURCE_SERVER;
        }

        List<String> names = new ArrayList<String>();
        List<ProviderInfo> providers = new ArrayList<ProviderInfo>();
        populateProvidersList(url, names, providers);

        final int numProviders = providers.size();
        for (int i = 0; i < numProviders; i++) {
            if (!providers.get(i).isSyncable) continue;
            final String name = names.get(i);
            for (String account : accounts) {
                scheduleSyncOperation(new SyncOperation(account, source, name, extras, delay));
                // TODO: remove this when Calendar supports multiple accounts. Until then
                // pretend that only the first account exists when syncing calendar.
                if ("calendar".equals(name)) {
                    break;
                }
            }
        }
    
public voidscheduleSyncOperation(android.content.SyncManager$SyncOperation syncOperation)
Create and schedule a SyncOperation.

param
syncOperation the SyncOperation to schedule

        // If this operation is expedited and there is a sync in progress then
        // reschedule the current operation and send a cancel for it.
        final boolean expedited = syncOperation.delay < 0;
        final ActiveSyncContext activeSyncContext = mActiveSyncContext;
        if (expedited && activeSyncContext != null) {
            final boolean activeIsExpedited = activeSyncContext.mSyncOperation.delay < 0;
            final boolean hasSameKey =
                    activeSyncContext.mSyncOperation.key.equals(syncOperation.key);
            // This request is expedited and there is a sync in progress.
            // Interrupt the current sync only if it is not expedited and if it has a different
            // key than the one we are scheduling.
            if (!activeIsExpedited && !hasSameKey) {
                rescheduleImmediately(activeSyncContext.mSyncOperation);
                sendSyncFinishedOrCanceledMessage(activeSyncContext,
                        null /* no result since this is a cancel */);
            }
        }

        boolean operationEnqueued;
        synchronized (mSyncQueue) {
            operationEnqueued = mSyncQueue.add(syncOperation);
        }

        if (operationEnqueued) {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "scheduleSyncOperation: enqueued " + syncOperation);
            }
            sendCheckAlarmsMessage();
        } else {
            if (Log.isLoggable(TAG, Log.VERBOSE)) {
                Log.v(TAG, "scheduleSyncOperation: dropping duplicate sync operation "
                        + syncOperation);
            }
        }
    
private voidscheduleSyncPollAlarm(long relativeAlarmTime)

        if (Log.isLoggable(TAG, Log.VERBOSE)) {
            Log.v(TAG, "scheduleSyncPollAlarm: relativeAlarmTime is " + relativeAlarmTime
                    + ", now is " + SystemClock.elapsedRealtime()
                    + ", delay is " + (relativeAlarmTime - SystemClock.elapsedRealtime()));
        }
        ensureAlarmService();
        mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, relativeAlarmTime,
                mSyncPollAlarmIntent);
    
private voidsendCheckAlarmsMessage()

        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CHECK_ALARMS");
        mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_CHECK_ALARMS);
    
private voidsendSyncAlarmMessage()

        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_ALARM");
        mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_SYNC_ALARM);
    
private voidsendSyncFinishedOrCanceledMessage(android.content.SyncManager$ActiveSyncContext syncContext, SyncResult syncResult)

        if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_FINISHED");
        Message msg = mSyncHandler.obtainMessage();
        msg.what = SyncHandler.MESSAGE_SYNC_FINISHED;
        msg.obj = new SyncHandlerMessagePayload(syncContext, syncResult);
        mSyncHandler.sendMessage(msg);
    
private voidsetStatusText(java.lang.String message)

        mStatusText = message;
    
public voidstartSync(android.net.Uri url, android.os.Bundle extras)
Initiate a sync for this given URL, or pass null for a full sync.

You'll start getting callbacks after this.

param
url The Uri of a specific provider to be synced, or null to sync all providers.
param
extras a Map of SyncAdapter specific information to control syncs of a specific provider. Can be null. Is ignored

        scheduleSync(url, extras, 0 /* no delay */);
    
public voidupdateHeartbeatTime()

        mHeartbeatTime = SystemClock.elapsedRealtime();
        ensureContentResolver();
        mContentResolver.notifyChange(Sync.Active.CONTENT_URI,
                null /* this change wasn't made through an observer */);
    
private voidwriteSyncPollTime(long when)

        File f = new File(SYNCMANAGER_PREFS_FILENAME);
        DataOutputStream str = null;
        try {
            str = new DataOutputStream(new FileOutputStream(f));
            str.writeLong(when);
        } catch (FileNotFoundException e) {
            Log.w(TAG, "error writing to file " + f, e);
        } catch (IOException e) {
            Log.w(TAG, "error writing to file " + f, e);
        } finally {
            if (str != null) {
                try {
                    str.close();
                } catch (IOException e) {
                    Log.w(TAG, "error closing file " + f, e);
                }
            }
        }