http://hi-android.info/src/android/content/SyncManager.java.html
SyncManager.java |
/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.content; import com.android.internal.R; import com.android.internal.util.ArrayUtils; import android.accounts.Account; import android.accounts.AccountManager; import android.accounts.OnAccountsUpdateListener; import android.app.AlarmManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.RegisteredServicesCache; import android.content.pm.ProviderInfo; import android.content.pm.RegisteredServicesCacheListener; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.os.WorkSource; import android.provider.Settings; import android.text.format.DateUtils; import android.text.format.Time; import android.util.EventLog; import android.util.Log; import android.util.Pair; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Random; import java.util.Collection; import java.util.concurrent.CountDownLatch; /** * @hide */ public class SyncManager implements OnAccountsUpdateListener { private static final String TAG = "SyncManager"; /** Delay a sync due to local changes this long. In milliseconds */ private static final long LOCAL_SYNC_DELAY; /** * 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 MAX_TIME_PER_SYNC; static { String localSyncDelayString = SystemProperties.get("sync.local_sync_delay"); long localSyncDelay = 30 * 1000; // 30 seconds if (localSyncDelayString != null) { try { localSyncDelay = Long.parseLong(localSyncDelayString); } catch (NumberFormatException nfe) { // ignore, use default } } LOCAL_SYNC_DELAY = localSyncDelay; String maxTimePerSyncString = SystemProperties.get("sync.max_time_per_sync"); long maxTimePerSync = 5 * 60 * 1000; // 5 minutes if (maxTimePerSyncString != null) { try { maxTimePerSync = Long.parseLong(maxTimePerSyncString); } catch (NumberFormatException nfe) { // ignore, use default } } MAX_TIME_PER_SYNC = maxTimePerSync; } private static final long SYNC_NOTIFICATION_DELAY = 30 * 1000; // 30 seconds /** * 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 INITIAL_SYNC_RETRY_TIME_IN_MS = 30 * 1000; // 30 seconds /** * Default the max sync retry time to this value. */ private static final long DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS = 60 * 60; // one hour /** * How long to wait before retrying a sync that failed due to one already being in progress. */ private static final int DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS = 10; /** * An error notification is sent if sync of any of the providers has been failing for this long. */ private static final long ERROR_NOTIFICATION_DELAY_MS = 1000 * 60 * 10; // 10 minutes private static final int INITIALIZATION_UNBIND_DELAY_MS = 5000; private static final String SYNC_WAKE_LOCK = "*sync*"; private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm"; private Context mContext; private volatile Account[] mAccounts = INITIAL_ACCOUNTS_ARRAY; volatile private PowerManager.WakeLock mSyncWakeLock; volatile private PowerManager.WakeLock mHandleAlarmWakeLock; volatile private boolean mDataConnectionIsConnected = false; volatile private boolean mStorageIsLow = false; private final NotificationManager mNotificationMgr; private AlarmManager mAlarmService = null; private final SyncStorageEngine mSyncStorageEngine; public final SyncQueue mSyncQueue; private ActiveSyncContext mActiveSyncContext = null; // set if the sync error indicator should be reported. private boolean mNeedSyncErrorNotification = false; // set if the sync active indicator should be reported private boolean mNeedSyncActiveNotification = false; private final PendingIntent mSyncAlarmIntent; // Synchronized on "this". Instead of using this directly one should instead call // its accessor, getConnManager(). private ConnectivityManager mConnManagerDoNotUseDirectly; private final SyncAdaptersCache mSyncAdapters; private BroadcastReceiver mStorageIntentReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Internal storage is low."); } mStorageIsLow = true; cancelActiveSync(null /* any account */, null /* any authority */); } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Internal storage is ok."); } mStorageIsLow = false; sendCheckAlarmsMessage(); } } }; private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { mSyncHandler.onBootCompleted(); } }; private BroadcastReceiver mBackgroundDataSettingChanged = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { if (getConnectivityManager().getBackgroundDataSetting()) { scheduleSync(null /* account */, null /* authority */, new Bundle(), 0 /* delay */, false /* onlyThoseWithUnknownSyncableState */); } } }; private static final Account[] INITIAL_ACCOUNTS_ARRAY = new Account[0]; public void onAccountsUpdated(Account[] accounts) { // remember if this was the first time this was called after an update final boolean justBootedUp = mAccounts == INITIAL_ACCOUNTS_ARRAY; mAccounts = accounts; // 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(accounts, 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(); if (mBootCompleted) { mSyncStorageEngine.doDatabaseCleanup(accounts); } if (accounts.length > 0) { // If this is the first time this was called after a bootup then // the accounts haven't really changed, instead they were just loaded // from the AccountManager. Otherwise at least one of the accounts // has a change. // // If there was a real account change then force a sync of all accounts. // This is a bit of overkill, but at least it will end up retrying syncs // that failed due to an authentication failure and thus will recover if the // account change was a password update. // // If this was the bootup case then don't sync everything, instead only // sync those that have an unknown syncable state, which will give them // a chance to set their syncable state. boolean onlyThoseWithUnkownSyncableState = justBootedUp; scheduleSync(null, null, null, 0 /* no delay */, onlyThoseWithUnkownSyncableState); } } private BroadcastReceiver mConnectivityIntentReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { NetworkInfo networkInfo = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO); NetworkInfo.State state = (networkInfo == null ? NetworkInfo.State.UNKNOWN : networkInfo.getState()); if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "received connectivity action. network info: " + networkInfo); } // only pay attention to the CONNECTED and DISCONNECTED states. // if connected, we are connected. // if disconnected, we may not be connected. in some cases, we may be connected on // a different network. // e.g., if switching from GPRS to WiFi, we may receive the CONNECTED to WiFi and // DISCONNECTED for GPRS in any order. if we receive the CONNECTED first, and then // a DISCONNECTED, we want to make sure we set mDataConnectionIsConnected to true // since we still have a WiFi connection. switch (state) { case CONNECTED: mDataConnectionIsConnected = true; break; case DISCONNECTED: if (intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false)) { mDataConnectionIsConnected = false; } else { mDataConnectionIsConnected = true; } break; default: // ignore the rest of the states -- leave our boolean alone. } if (mDataConnectionIsConnected) { sendCheckAlarmsMessage(); } } }; private BroadcastReceiver mShutdownIntentReceiver = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { Log.w(TAG, "Writing sync state before shutdown..."); getSyncStorageEngine().writeAllState(); } }; private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM"; private final SyncHandler mSyncHandler; private final Handler mMainHandler; private volatile boolean mBootCompleted = false; private ConnectivityManager getConnectivityManager() { synchronized (this) { if (mConnManagerDoNotUseDirectly == null) { mConnManagerDoNotUseDirectly = (ConnectivityManager)mContext.getSystemService( Context.CONNECTIVITY_SERVICE); } return mConnManagerDoNotUseDirectly; } } 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; HandlerThread syncThread = new HandlerThread("SyncHandlerThread", Process.THREAD_PRIORITY_BACKGROUND); syncThread.start(); mSyncHandler = new SyncHandler(syncThread.getLooper()); mMainHandler = new Handler(mContext.getMainLooper()); mSyncAdapters = new SyncAdaptersCache(mContext); mSyncAdapters.setListener(new RegisteredServicesCacheListener<SyncAdapterType>() { public void onServiceChanged(SyncAdapterType type, boolean removed) { if (!removed) { scheduleSync(null, type.authority, null, 0 /* no delay */, false /* onlyThoseWithUnkownSyncableState */); } } }, mSyncHandler); mSyncAlarmIntent = PendingIntent.getBroadcast( mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0); IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); context.registerReceiver(mConnectivityIntentReceiver, intentFilter); if (!factoryTest) { intentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED); context.registerReceiver(mBootCompletedReceiver, intentFilter); } intentFilter = new IntentFilter(ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED); context.registerReceiver(mBackgroundDataSettingChanged, intentFilter); intentFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW); intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); context.registerReceiver(mStorageIntentReceiver, intentFilter); intentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN); intentFilter.setPriority(100); context.registerReceiver(mShutdownIntentReceiver, 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); mSyncStorageEngine.addStatusChangeListener( ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, new ISyncStatusObserver.Stub() { public void onStatusChanged(int which) { // force the sync loop to run if the settings change sendCheckAlarmsMessage(); } }); if (!factoryTest) { AccountManager.get(mContext).addOnAccountsUpdatedListener(SyncManager.this, mSyncHandler, false /* updateImmediately */); // do this synchronously to ensure we have the accounts before this call returns onAccountsUpdated(AccountManager.get(mContext).getAccounts()); } } /** * Return a random value v that satisfies minValue <= v < maxValue. The difference between * maxValue and minValue must be less than Integer.MAX_VALUE. */ private long jitterize(long minValue, long maxValue) { 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); } public SyncStorageEngine getSyncStorageEngine() { return mSyncStorageEngine; } private void ensureAlarmService() { if (mAlarmService == null) { mAlarmService = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); } } private void initializeSyncAdapter(Account account, String authority) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "initializeSyncAdapter: " + account + ", authority " + authority); } SyncAdapterType syncAdapterType = SyncAdapterType.newKey(authority, account.type); RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo = mSyncAdapters.getServiceInfo(syncAdapterType); if (syncAdapterInfo == null) { Log.w(TAG, "can't find a sync adapter for " + syncAdapterType + ", removing"); mSyncStorageEngine.removeAuthority(account, authority); return; } Intent intent = new Intent(); intent.setAction("android.content.SyncAdapter"); intent.setComponent(syncAdapterInfo.componentName); if (!mContext.bindService(intent, new InitializerServiceConnection(account, authority, mContext, mMainHandler), Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND)) { Log.w(TAG, "initializeSyncAdapter: failed to bind to " + intent); } } private static class InitializerServiceConnection implements ServiceConnection { private final Account mAccount; private final String mAuthority; private final Handler mHandler; private volatile Context mContext; private volatile boolean mInitialized; public InitializerServiceConnection(Account account, String authority, Context context, Handler handler) { mAccount = account; mAuthority = authority; mContext = context; mHandler = handler; mInitialized = false; } public void onServiceConnected(ComponentName name, IBinder service) { try { if (!mInitialized) { mInitialized = true; if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "calling initialize: " + mAccount + ", authority " + mAuthority); } ISyncAdapter.Stub.asInterface(service).initialize(mAccount, mAuthority); } } catch (RemoteException e) { // doesn't matter, we will retry again later Log.d(TAG, "error while initializing: " + mAccount + ", authority " + mAuthority, e); } finally { // give the sync adapter time to initialize before unbinding from it // TODO: change this API to not rely on this timing, http://b/2500805 mHandler.postDelayed(new Runnable() { public void run() { if (mContext != null) { mContext.unbindService(InitializerServiceConnection.this); mContext = null; } } }, INITIALIZATION_UNBIND_DELAY_MS); } } public void onServiceDisconnected(ComponentName name) { if (mContext != null) { mContext.unbindService(InitializerServiceConnection.this); mContext = null; } } } /** * 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). * * <p>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. * * <p>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). * * <p>You'll start getting callbacks after this. * * @param requestedAccount the account to sync, may be null to signify all accounts * @param requestedAuthority the authority to sync, may be null to indicate all authorities * @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 */ public void scheduleSync(Account requestedAccount, String requestedAuthority, Bundle extras, long delay, boolean onlyThoseWithUnkownSyncableState) { boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); final boolean backgroundDataUsageAllowed = !mBootCompleted || getConnectivityManager().getBackgroundDataSetting(); 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 } Account[] accounts; if (requestedAccount != null) { accounts = new Account[]{requestedAccount}; } else { // if the accounts aren't configured yet then we can't support an account-less // sync request accounts = mAccounts; 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; } // 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()) { 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 fo
评论