Home | History | Annotate | Download | only in content
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.content;
     18 
     19 import com.android.internal.R;
     20 import com.android.internal.util.ArrayUtils;
     21 
     22 import android.accounts.Account;
     23 import android.accounts.AccountManager;
     24 import android.accounts.OnAccountsUpdateListener;
     25 import android.app.AlarmManager;
     26 import android.app.Notification;
     27 import android.app.NotificationManager;
     28 import android.app.PendingIntent;
     29 import android.content.pm.ApplicationInfo;
     30 import android.content.pm.PackageManager;
     31 import android.content.pm.ResolveInfo;
     32 import android.content.pm.RegisteredServicesCache;
     33 import android.content.pm.ProviderInfo;
     34 import android.content.pm.RegisteredServicesCacheListener;
     35 import android.net.ConnectivityManager;
     36 import android.net.NetworkInfo;
     37 import android.os.Bundle;
     38 import android.os.Handler;
     39 import android.os.HandlerThread;
     40 import android.os.IBinder;
     41 import android.os.Looper;
     42 import android.os.Message;
     43 import android.os.PowerManager;
     44 import android.os.Process;
     45 import android.os.RemoteException;
     46 import android.os.SystemClock;
     47 import android.os.SystemProperties;
     48 import android.provider.Settings;
     49 import android.text.format.DateUtils;
     50 import android.text.format.Time;
     51 import android.util.EventLog;
     52 import android.util.Log;
     53 import android.util.Pair;
     54 
     55 import java.io.FileDescriptor;
     56 import java.io.PrintWriter;
     57 import java.util.ArrayList;
     58 import java.util.HashSet;
     59 import java.util.List;
     60 import java.util.Random;
     61 import java.util.Collection;
     62 import java.util.concurrent.CountDownLatch;
     63 
     64 /**
     65  * @hide
     66  */
     67 public class SyncManager implements OnAccountsUpdateListener {
     68     private static final String TAG = "SyncManager";
     69 
     70     /** Delay a sync due to local changes this long. In milliseconds */
     71     private static final long LOCAL_SYNC_DELAY;
     72 
     73     /**
     74      * If a sync takes longer than this and the sync queue is not empty then we will
     75      * cancel it and add it back to the end of the sync queue. In milliseconds.
     76      */
     77     private static final long MAX_TIME_PER_SYNC;
     78 
     79     static {
     80         String localSyncDelayString = SystemProperties.get("sync.local_sync_delay");
     81         long localSyncDelay = 30 * 1000; // 30 seconds
     82         if (localSyncDelayString != null) {
     83             try {
     84                 localSyncDelay = Long.parseLong(localSyncDelayString);
     85             } catch (NumberFormatException nfe) {
     86                 // ignore, use default
     87             }
     88         }
     89         LOCAL_SYNC_DELAY = localSyncDelay;
     90 
     91         String maxTimePerSyncString = SystemProperties.get("sync.max_time_per_sync");
     92         long maxTimePerSync = 5 * 60 * 1000; // 5 minutes
     93         if (maxTimePerSyncString != null) {
     94             try {
     95                 maxTimePerSync = Long.parseLong(maxTimePerSyncString);
     96             } catch (NumberFormatException nfe) {
     97                 // ignore, use default
     98             }
     99         }
    100         MAX_TIME_PER_SYNC = maxTimePerSync;
    101     }
    102 
    103     private static final long SYNC_NOTIFICATION_DELAY = 30 * 1000; // 30 seconds
    104 
    105     /**
    106      * When retrying a sync for the first time use this delay. After that
    107      * the retry time will double until it reached MAX_SYNC_RETRY_TIME.
    108      * In milliseconds.
    109      */
    110     private static final long INITIAL_SYNC_RETRY_TIME_IN_MS = 30 * 1000; // 30 seconds
    111 
    112     /**
    113      * Default the max sync retry time to this value.
    114      */
    115     private static final long DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS = 60 * 60; // one hour
    116 
    117     /**
    118      * How long to wait before retrying a sync that failed due to one already being in progress.
    119      */
    120     private static final int DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS = 10;
    121 
    122     /**
    123      * An error notification is sent if sync of any of the providers has been failing for this long.
    124      */
    125     private static final long ERROR_NOTIFICATION_DELAY_MS = 1000 * 60 * 10; // 10 minutes
    126 
    127     private static final int INITIALIZATION_UNBIND_DELAY_MS = 5000;
    128 
    129     private static final String SYNC_WAKE_LOCK = "SyncManagerSyncWakeLock";
    130     private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarmWakeLock";
    131 
    132     private Context mContext;
    133 
    134     private volatile Account[] mAccounts = INITIAL_ACCOUNTS_ARRAY;
    135 
    136     volatile private PowerManager.WakeLock mSyncWakeLock;
    137     volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
    138     volatile private boolean mDataConnectionIsConnected = false;
    139     volatile private boolean mStorageIsLow = false;
    140 
    141     private final NotificationManager mNotificationMgr;
    142     private AlarmManager mAlarmService = null;
    143 
    144     private final SyncStorageEngine mSyncStorageEngine;
    145     public final SyncQueue mSyncQueue;
    146 
    147     private ActiveSyncContext mActiveSyncContext = null;
    148 
    149     // set if the sync error indicator should be reported.
    150     private boolean mNeedSyncErrorNotification = false;
    151     // set if the sync active indicator should be reported
    152     private boolean mNeedSyncActiveNotification = false;
    153 
    154     private final PendingIntent mSyncAlarmIntent;
    155     // Synchronized on "this". Instead of using this directly one should instead call
    156     // its accessor, getConnManager().
    157     private ConnectivityManager mConnManagerDoNotUseDirectly;
    158 
    159     private final SyncAdaptersCache mSyncAdapters;
    160 
    161     private BroadcastReceiver mStorageIntentReceiver =
    162             new BroadcastReceiver() {
    163                 public void onReceive(Context context, Intent intent) {
    164                     String action = intent.getAction();
    165                     if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
    166                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    167                             Log.v(TAG, "Internal storage is low.");
    168                         }
    169                         mStorageIsLow = true;
    170                         cancelActiveSync(null /* any account */, null /* any authority */);
    171                     } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
    172                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    173                             Log.v(TAG, "Internal storage is ok.");
    174                         }
    175                         mStorageIsLow = false;
    176                         sendCheckAlarmsMessage();
    177                     }
    178                 }
    179             };
    180 
    181     private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
    182         public void onReceive(Context context, Intent intent) {
    183             mSyncHandler.onBootCompleted();
    184         }
    185     };
    186 
    187     private BroadcastReceiver mBackgroundDataSettingChanged = new BroadcastReceiver() {
    188         public void onReceive(Context context, Intent intent) {
    189             if (getConnectivityManager().getBackgroundDataSetting()) {
    190                 scheduleSync(null /* account */, null /* authority */, new Bundle(), 0 /* delay */,
    191                         false /* onlyThoseWithUnknownSyncableState */);
    192             }
    193         }
    194     };
    195 
    196     private static final Account[] INITIAL_ACCOUNTS_ARRAY = new Account[0];
    197 
    198     public void onAccountsUpdated(Account[] accounts) {
    199         // remember if this was the first time this was called after an update
    200         final boolean justBootedUp = mAccounts == INITIAL_ACCOUNTS_ARRAY;
    201         mAccounts = accounts;
    202 
    203         // if a sync is in progress yet it is no longer in the accounts list,
    204         // cancel it
    205         ActiveSyncContext activeSyncContext = mActiveSyncContext;
    206         if (activeSyncContext != null) {
    207             if (!ArrayUtils.contains(accounts, activeSyncContext.mSyncOperation.account)) {
    208                 Log.d(TAG, "canceling sync since the account has been removed");
    209                 sendSyncFinishedOrCanceledMessage(activeSyncContext,
    210                         null /* no result since this is a cancel */);
    211             }
    212         }
    213 
    214         // we must do this since we don't bother scheduling alarms when
    215         // the accounts are not set yet
    216         sendCheckAlarmsMessage();
    217 
    218         if (mBootCompleted) {
    219             mSyncStorageEngine.doDatabaseCleanup(accounts);
    220         }
    221 
    222         if (accounts.length > 0) {
    223             // If this is the first time this was called after a bootup then
    224             // the accounts haven't really changed, instead they were just loaded
    225             // from the AccountManager. Otherwise at least one of the accounts
    226             // has a change.
    227             //
    228             // If there was a real account change then force a sync of all accounts.
    229             // This is a bit of overkill, but at least it will end up retrying syncs
    230             // that failed due to an authentication failure and thus will recover if the
    231             // account change was a password update.
    232             //
    233             // If this was the bootup case then don't sync everything, instead only
    234             // sync those that have an unknown syncable state, which will give them
    235             // a chance to set their syncable state.
    236             boolean onlyThoseWithUnkownSyncableState = justBootedUp;
    237             scheduleSync(null, null, null, 0 /* no delay */, onlyThoseWithUnkownSyncableState);
    238         }
    239     }
    240 
    241     private BroadcastReceiver mConnectivityIntentReceiver =
    242             new BroadcastReceiver() {
    243         public void onReceive(Context context, Intent intent) {
    244             NetworkInfo networkInfo =
    245                     intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
    246             NetworkInfo.State state = (networkInfo == null ? NetworkInfo.State.UNKNOWN :
    247                     networkInfo.getState());
    248             if (Log.isLoggable(TAG, Log.VERBOSE)) {
    249                 Log.v(TAG, "received connectivity action.  network info: " + networkInfo);
    250             }
    251 
    252             // only pay attention to the CONNECTED and DISCONNECTED states.
    253             // if connected, we are connected.
    254             // if disconnected, we may not be connected.  in some cases, we may be connected on
    255             // a different network.
    256             // e.g., if switching from GPRS to WiFi, we may receive the CONNECTED to WiFi and
    257             // DISCONNECTED for GPRS in any order.  if we receive the CONNECTED first, and then
    258             // a DISCONNECTED, we want to make sure we set mDataConnectionIsConnected to true
    259             // since we still have a WiFi connection.
    260             switch (state) {
    261                 case CONNECTED:
    262                     mDataConnectionIsConnected = true;
    263                     break;
    264                 case DISCONNECTED:
    265                     if (intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false)) {
    266                         mDataConnectionIsConnected = false;
    267                     } else {
    268                         mDataConnectionIsConnected = true;
    269                     }
    270                     break;
    271                 default:
    272                     // ignore the rest of the states -- leave our boolean alone.
    273             }
    274             if (mDataConnectionIsConnected) {
    275                 sendCheckAlarmsMessage();
    276             }
    277         }
    278     };
    279 
    280     private BroadcastReceiver mShutdownIntentReceiver =
    281             new BroadcastReceiver() {
    282         public void onReceive(Context context, Intent intent) {
    283             Log.w(TAG, "Writing sync state before shutdown...");
    284             getSyncStorageEngine().writeAllState();
    285         }
    286     };
    287 
    288     private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM";
    289     private final SyncHandler mSyncHandler;
    290     private final Handler mMainHandler;
    291 
    292     private volatile boolean mBootCompleted = false;
    293 
    294     private ConnectivityManager getConnectivityManager() {
    295         synchronized (this) {
    296             if (mConnManagerDoNotUseDirectly == null) {
    297                 mConnManagerDoNotUseDirectly = (ConnectivityManager)mContext.getSystemService(
    298                         Context.CONNECTIVITY_SERVICE);
    299             }
    300             return mConnManagerDoNotUseDirectly;
    301         }
    302     }
    303 
    304     public SyncManager(Context context, boolean factoryTest) {
    305         // Initialize the SyncStorageEngine first, before registering observers
    306         // and creating threads and so on; it may fail if the disk is full.
    307         SyncStorageEngine.init(context);
    308         mSyncStorageEngine = SyncStorageEngine.getSingleton();
    309         mSyncQueue = new SyncQueue(mSyncStorageEngine);
    310 
    311         mContext = context;
    312 
    313         HandlerThread syncThread = new HandlerThread("SyncHandlerThread",
    314                 Process.THREAD_PRIORITY_BACKGROUND);
    315         syncThread.start();
    316         mSyncHandler = new SyncHandler(syncThread.getLooper());
    317         mMainHandler = new Handler(mContext.getMainLooper());
    318 
    319         mSyncAdapters = new SyncAdaptersCache(mContext);
    320         mSyncAdapters.setListener(new RegisteredServicesCacheListener<SyncAdapterType>() {
    321             public void onServiceChanged(SyncAdapterType type, boolean removed) {
    322                 if (!removed) {
    323                     scheduleSync(null, type.authority, null, 0 /* no delay */,
    324                             false /* onlyThoseWithUnkownSyncableState */);
    325                 }
    326             }
    327         }, mSyncHandler);
    328 
    329         mSyncAlarmIntent = PendingIntent.getBroadcast(
    330                 mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0);
    331 
    332         IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
    333         context.registerReceiver(mConnectivityIntentReceiver, intentFilter);
    334 
    335         if (!factoryTest) {
    336             intentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
    337             context.registerReceiver(mBootCompletedReceiver, intentFilter);
    338         }
    339 
    340         intentFilter = new IntentFilter(ConnectivityManager.ACTION_BACKGROUND_DATA_SETTING_CHANGED);
    341         context.registerReceiver(mBackgroundDataSettingChanged, intentFilter);
    342 
    343         intentFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
    344         intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
    345         context.registerReceiver(mStorageIntentReceiver, intentFilter);
    346 
    347         intentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
    348         intentFilter.setPriority(100);
    349         context.registerReceiver(mShutdownIntentReceiver, intentFilter);
    350 
    351         if (!factoryTest) {
    352             mNotificationMgr = (NotificationManager)
    353                 context.getSystemService(Context.NOTIFICATION_SERVICE);
    354             context.registerReceiver(new SyncAlarmIntentReceiver(),
    355                     new IntentFilter(ACTION_SYNC_ALARM));
    356         } else {
    357             mNotificationMgr = null;
    358         }
    359         PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
    360         mSyncWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, SYNC_WAKE_LOCK);
    361         mSyncWakeLock.setReferenceCounted(false);
    362 
    363         // This WakeLock is used to ensure that we stay awake between the time that we receive
    364         // a sync alarm notification and when we finish processing it. We need to do this
    365         // because we don't do the work in the alarm handler, rather we do it in a message
    366         // handler.
    367         mHandleAlarmWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
    368                 HANDLE_SYNC_ALARM_WAKE_LOCK);
    369         mHandleAlarmWakeLock.setReferenceCounted(false);
    370 
    371         mSyncStorageEngine.addStatusChangeListener(
    372                 ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, new ISyncStatusObserver.Stub() {
    373             public void onStatusChanged(int which) {
    374                 // force the sync loop to run if the settings change
    375                 sendCheckAlarmsMessage();
    376             }
    377         });
    378 
    379         if (!factoryTest) {
    380             AccountManager.get(mContext).addOnAccountsUpdatedListener(SyncManager.this,
    381                 mSyncHandler, false /* updateImmediately */);
    382             // do this synchronously to ensure we have the accounts before this call returns
    383             onAccountsUpdated(AccountManager.get(mContext).getAccounts());
    384         }
    385     }
    386 
    387     /**
    388      * Return a random value v that satisfies minValue <= v < maxValue. The difference between
    389      * maxValue and minValue must be less than Integer.MAX_VALUE.
    390      */
    391     private long jitterize(long minValue, long maxValue) {
    392         Random random = new Random(SystemClock.elapsedRealtime());
    393         long spread = maxValue - minValue;
    394         if (spread > Integer.MAX_VALUE) {
    395             throw new IllegalArgumentException("the difference between the maxValue and the "
    396                     + "minValue must be less than " + Integer.MAX_VALUE);
    397         }
    398         return minValue + random.nextInt((int)spread);
    399     }
    400 
    401     public SyncStorageEngine getSyncStorageEngine() {
    402         return mSyncStorageEngine;
    403     }
    404 
    405     private void ensureAlarmService() {
    406         if (mAlarmService == null) {
    407             mAlarmService = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
    408         }
    409     }
    410 
    411     private void initializeSyncAdapter(Account account, String authority) {
    412         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    413             Log.v(TAG, "initializeSyncAdapter: " + account + ", authority " + authority);
    414         }
    415         SyncAdapterType syncAdapterType = SyncAdapterType.newKey(authority, account.type);
    416         RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
    417                 mSyncAdapters.getServiceInfo(syncAdapterType);
    418         if (syncAdapterInfo == null) {
    419             Log.w(TAG, "can't find a sync adapter for " + syncAdapterType + ", removing");
    420             mSyncStorageEngine.removeAuthority(account, authority);
    421             return;
    422         }
    423 
    424         Intent intent = new Intent();
    425         intent.setAction("android.content.SyncAdapter");
    426         intent.setComponent(syncAdapterInfo.componentName);
    427         if (!mContext.bindService(intent, new InitializerServiceConnection(account, authority, mContext,
    428                 mMainHandler),
    429                 Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND)) {
    430             Log.w(TAG, "initializeSyncAdapter: failed to bind to " + intent);
    431         }
    432     }
    433 
    434     private static class InitializerServiceConnection implements ServiceConnection {
    435         private final Account mAccount;
    436         private final String mAuthority;
    437         private final Handler mHandler;
    438         private volatile Context mContext;
    439         private volatile boolean mInitialized;
    440 
    441         public InitializerServiceConnection(Account account, String authority, Context context,
    442                 Handler handler) {
    443             mAccount = account;
    444             mAuthority = authority;
    445             mContext = context;
    446             mHandler = handler;
    447             mInitialized = false;
    448         }
    449 
    450         public void onServiceConnected(ComponentName name, IBinder service) {
    451             try {
    452                 if (!mInitialized) {
    453                     mInitialized = true;
    454                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
    455                         Log.v(TAG, "calling initialize: " + mAccount + ", authority " + mAuthority);
    456                     }
    457                     ISyncAdapter.Stub.asInterface(service).initialize(mAccount, mAuthority);
    458                 }
    459             } catch (RemoteException e) {
    460                 // doesn't matter, we will retry again later
    461                 Log.d(TAG, "error while initializing: " + mAccount + ", authority " + mAuthority,
    462                         e);
    463             } finally {
    464                 // give the sync adapter time to initialize before unbinding from it
    465                 // TODO: change this API to not rely on this timing, http://b/2500805
    466                 mHandler.postDelayed(new Runnable() {
    467                     public void run() {
    468                         if (mContext != null) {
    469                             mContext.unbindService(InitializerServiceConnection.this);
    470                             mContext = null;
    471                         }
    472                     }
    473                 }, INITIALIZATION_UNBIND_DELAY_MS);
    474             }
    475         }
    476 
    477         public void onServiceDisconnected(ComponentName name) {
    478             if (mContext != null) {
    479                 mContext.unbindService(InitializerServiceConnection.this);
    480                 mContext = null;
    481             }
    482         }
    483 
    484     }
    485 
    486     /**
    487      * Initiate a sync. This can start a sync for all providers
    488      * (pass null to url, set onlyTicklable to false), only those
    489      * providers that are marked as ticklable (pass null to url,
    490      * set onlyTicklable to true), or a specific provider (set url
    491      * to the content url of the provider).
    492      *
    493      * <p>If the ContentResolver.SYNC_EXTRAS_UPLOAD boolean in extras is
    494      * true then initiate a sync that just checks for local changes to send
    495      * to the server, otherwise initiate a sync that first gets any
    496      * changes from the server before sending local changes back to
    497      * the server.
    498      *
    499      * <p>If a specific provider is being synced (the url is non-null)
    500      * then the extras can contain SyncAdapter-specific information
    501      * to control what gets synced (e.g. which specific feed to sync).
    502      *
    503      * <p>You'll start getting callbacks after this.
    504      *
    505      * @param requestedAccount the account to sync, may be null to signify all accounts
    506      * @param requestedAuthority the authority to sync, may be null to indicate all authorities
    507      * @param extras a Map of SyncAdapter-specific information to control
    508 *          syncs of a specific provider. Can be null. Is ignored
    509 *          if the url is null.
    510      * @param delay how many milliseconds in the future to wait before performing this
    511      * @param onlyThoseWithUnkownSyncableState
    512      */
    513     public void scheduleSync(Account requestedAccount, String requestedAuthority,
    514             Bundle extras, long delay, boolean onlyThoseWithUnkownSyncableState) {
    515         boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
    516 
    517         final boolean backgroundDataUsageAllowed = !mBootCompleted ||
    518                 getConnectivityManager().getBackgroundDataSetting();
    519 
    520         if (extras == null) extras = new Bundle();
    521 
    522         Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
    523         if (expedited) {
    524             delay = -1; // this means schedule at the front of the queue
    525         }
    526 
    527         Account[] accounts;
    528         if (requestedAccount != null) {
    529             accounts = new Account[]{requestedAccount};
    530         } else {
    531             // if the accounts aren't configured yet then we can't support an account-less
    532             // sync request
    533             accounts = mAccounts;
    534             if (accounts.length == 0) {
    535                 if (isLoggable) {
    536                     Log.v(TAG, "scheduleSync: no accounts configured, dropping");
    537                 }
    538                 return;
    539             }
    540         }
    541 
    542         final boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);
    543         final boolean manualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
    544         if (manualSync) {
    545             extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
    546             extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
    547         }
    548         final boolean ignoreSettings =
    549                 extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
    550 
    551         int source;
    552         if (uploadOnly) {
    553             source = SyncStorageEngine.SOURCE_LOCAL;
    554         } else if (manualSync) {
    555             source = SyncStorageEngine.SOURCE_USER;
    556         } else if (requestedAuthority == null) {
    557             source = SyncStorageEngine.SOURCE_POLL;
    558         } else {
    559             // this isn't strictly server, since arbitrary callers can (and do) request
    560             // a non-forced two-way sync on a specific url
    561             source = SyncStorageEngine.SOURCE_SERVER;
    562         }
    563 
    564         // Compile a list of authorities that have sync adapters.
    565         // For each authority sync each account that matches a sync adapter.
    566         final HashSet<String> syncableAuthorities = new HashSet<String>();
    567         for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter :
    568                 mSyncAdapters.getAllServices()) {
    569             syncableAuthorities.add(syncAdapter.type.authority);
    570         }
    571 
    572         // if the url was specified then replace the list of authorities with just this authority
    573         // or clear it if this authority isn't syncable
    574         if (requestedAuthority != null) {
    575             final boolean hasSyncAdapter = syncableAuthorities.contains(requestedAuthority);
    576             syncableAuthorities.clear();
    577             if (hasSyncAdapter) syncableAuthorities.add(requestedAuthority);
    578         }
    579 
    580         final boolean masterSyncAutomatically = mSyncStorageEngine.getMasterSyncAutomatically();
    581 
    582         for (String authority : syncableAuthorities) {
    583             for (Account account : accounts) {
    584                 int isSyncable = mSyncStorageEngine.getIsSyncable(account, authority);
    585                 if (isSyncable == 0) {
    586                     continue;
    587                 }
    588                 if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) {
    589                     continue;
    590                 }
    591                 final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
    592                         mSyncAdapters.getServiceInfo(
    593                                 SyncAdapterType.newKey(authority, account.type));
    594                 if (syncAdapterInfo != null) {
    595                     if (!syncAdapterInfo.type.supportsUploading() && uploadOnly) {
    596                         continue;
    597                     }
    598 
    599                     // always allow if the isSyncable state is unknown
    600                     boolean syncAllowed =
    601                             (isSyncable < 0)
    602                             || ignoreSettings
    603                             || (backgroundDataUsageAllowed && masterSyncAutomatically
    604                                 && mSyncStorageEngine.getSyncAutomatically(account, authority));
    605                     if (!syncAllowed) {
    606                         if (isLoggable) {
    607                             Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority
    608                                     + " is not allowed, dropping request");
    609                         }
    610                         continue;
    611                     }
    612 
    613                     if (isLoggable) {
    614                         Log.v(TAG, "scheduleSync:"
    615                                 + " delay " + delay
    616                                 + ", source " + source
    617                                 + ", account " + account
    618                                 + ", authority " + authority
    619                                 + ", extras " + extras);
    620                     }
    621                     scheduleSyncOperation(
    622                             new SyncOperation(account, source, authority, extras, delay));
    623                 }
    624             }
    625         }
    626     }
    627 
    628     public void scheduleLocalSync(Account account, String authority) {
    629         final Bundle extras = new Bundle();
    630         extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
    631         scheduleSync(account, authority, extras, LOCAL_SYNC_DELAY,
    632                 false /* onlyThoseWithUnkownSyncableState */);
    633     }
    634 
    635     public SyncAdapterType[] getSyncAdapterTypes() {
    636         final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> serviceInfos =
    637                 mSyncAdapters.getAllServices();
    638         SyncAdapterType[] types = new SyncAdapterType[serviceInfos.size()];
    639         int i = 0;
    640         for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> serviceInfo : serviceInfos) {
    641             types[i] = serviceInfo.type;
    642             ++i;
    643         }
    644         return types;
    645     }
    646 
    647     private void sendSyncAlarmMessage() {
    648         if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_ALARM");
    649         mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_SYNC_ALARM);
    650     }
    651 
    652     private void sendCheckAlarmsMessage() {
    653         if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CHECK_ALARMS");
    654         mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_CHECK_ALARMS);
    655     }
    656 
    657     private void sendSyncFinishedOrCanceledMessage(ActiveSyncContext syncContext,
    658             SyncResult syncResult) {
    659         if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_FINISHED");
    660         Message msg = mSyncHandler.obtainMessage();
    661         msg.what = SyncHandler.MESSAGE_SYNC_FINISHED;
    662         msg.obj = new SyncHandlerMessagePayload(syncContext, syncResult);
    663         mSyncHandler.sendMessage(msg);
    664     }
    665 
    666     class SyncHandlerMessagePayload {
    667         public final ActiveSyncContext activeSyncContext;
    668         public final SyncResult syncResult;
    669 
    670         SyncHandlerMessagePayload(ActiveSyncContext syncContext, SyncResult syncResult) {
    671             this.activeSyncContext = syncContext;
    672             this.syncResult = syncResult;
    673         }
    674     }
    675 
    676     class SyncAlarmIntentReceiver extends BroadcastReceiver {
    677         public void onReceive(Context context, Intent intent) {
    678             mHandleAlarmWakeLock.acquire();
    679             sendSyncAlarmMessage();
    680         }
    681     }
    682 
    683     private void clearBackoffSetting(SyncOperation op) {
    684         mSyncStorageEngine.setBackoff(op.account, op.authority,
    685                 SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
    686     }
    687 
    688     private void increaseBackoffSetting(SyncOperation op) {
    689         final long now = SystemClock.elapsedRealtime();
    690 
    691         final Pair<Long, Long> previousSettings =
    692                 mSyncStorageEngine.getBackoff(op.account, op.authority);
    693         long newDelayInMs;
    694         if (previousSettings == null || previousSettings.second <= 0) {
    695             // The initial delay is the jitterized INITIAL_SYNC_RETRY_TIME_IN_MS
    696             newDelayInMs = jitterize(INITIAL_SYNC_RETRY_TIME_IN_MS,
    697                     (long)(INITIAL_SYNC_RETRY_TIME_IN_MS * 1.1));
    698         } else {
    699             // Subsequent delays are the double of the previous delay
    700             newDelayInMs = previousSettings.second * 2;
    701         }
    702 
    703         // Cap the delay
    704         long maxSyncRetryTimeInSeconds = Settings.Secure.getLong(mContext.getContentResolver(),
    705                 Settings.Secure.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
    706                 DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS);
    707         if (newDelayInMs > maxSyncRetryTimeInSeconds * 1000) {
    708             newDelayInMs = maxSyncRetryTimeInSeconds * 1000;
    709         }
    710 
    711         mSyncStorageEngine.setBackoff(op.account, op.authority,
    712                 now + newDelayInMs, newDelayInMs);
    713     }
    714 
    715     private void setDelayUntilTime(SyncOperation op, long delayUntilSeconds) {
    716         final long delayUntil = delayUntilSeconds * 1000;
    717         final long absoluteNow = System.currentTimeMillis();
    718         long newDelayUntilTime;
    719         if (delayUntil > absoluteNow) {
    720             newDelayUntilTime = SystemClock.elapsedRealtime() + (delayUntil - absoluteNow);
    721         } else {
    722             newDelayUntilTime = 0;
    723         }
    724         mSyncStorageEngine.setDelayUntilTime(op.account, op.authority, newDelayUntilTime);
    725     }
    726 
    727     /**
    728      * Cancel the active sync if it matches the authority and account.
    729      * @param account limit the cancelations to syncs with this account, if non-null
    730      * @param authority limit the cancelations to syncs with this authority, if non-null
    731      */
    732     public void cancelActiveSync(Account account, String authority) {
    733         ActiveSyncContext activeSyncContext = mActiveSyncContext;
    734         if (activeSyncContext != null) {
    735             // if an authority was specified then only cancel the sync if it matches
    736             if (account != null) {
    737                 if (!account.equals(activeSyncContext.mSyncOperation.account)) {
    738                     return;
    739                 }
    740             }
    741             // if an account was specified then only cancel the sync if it matches
    742             if (authority != null) {
    743                 if (!authority.equals(activeSyncContext.mSyncOperation.authority)) {
    744                     return;
    745                 }
    746             }
    747             sendSyncFinishedOrCanceledMessage(activeSyncContext,
    748                     null /* no result since this is a cancel */);
    749         }
    750     }
    751 
    752     /**
    753      * Create and schedule a SyncOperation.
    754      *
    755      * @param syncOperation the SyncOperation to schedule
    756      */
    757     public void scheduleSyncOperation(SyncOperation syncOperation) {
    758         // If this operation is expedited and there is a sync in progress then
    759         // reschedule the current operation and send a cancel for it.
    760         final ActiveSyncContext activeSyncContext = mActiveSyncContext;
    761         if (syncOperation.expedited && activeSyncContext != null) {
    762             final boolean hasSameKey =
    763                     activeSyncContext.mSyncOperation.key.equals(syncOperation.key);
    764             // This request is expedited and there is a sync in progress.
    765             // Interrupt the current sync only if it is not expedited and if it has a different
    766             // key than the one we are scheduling.
    767             if (!activeSyncContext.mSyncOperation.expedited && !hasSameKey) {
    768                 scheduleSyncOperation(new SyncOperation(activeSyncContext.mSyncOperation));
    769                 sendSyncFinishedOrCanceledMessage(activeSyncContext,
    770                         null /* no result since this is a cancel */);
    771             }
    772         }
    773 
    774         boolean queueChanged;
    775         synchronized (mSyncQueue) {
    776             queueChanged = mSyncQueue.add(syncOperation);
    777         }
    778 
    779         if (queueChanged) {
    780             if (Log.isLoggable(TAG, Log.VERBOSE)) {
    781                 Log.v(TAG, "scheduleSyncOperation: enqueued " + syncOperation);
    782             }
    783             sendCheckAlarmsMessage();
    784         } else {
    785             if (Log.isLoggable(TAG, Log.VERBOSE)) {
    786                 Log.v(TAG, "scheduleSyncOperation: dropping duplicate sync operation "
    787                         + syncOperation);
    788             }
    789         }
    790     }
    791 
    792     /**
    793      * Remove scheduled sync operations.
    794      * @param account limit the removals to operations with this account, if non-null
    795      * @param authority limit the removals to operations with this authority, if non-null
    796      */
    797     public void clearScheduledSyncOperations(Account account, String authority) {
    798         mSyncStorageEngine.setBackoff(account, authority,
    799                 SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
    800         synchronized (mSyncQueue) {
    801             mSyncQueue.remove(account, authority);
    802         }
    803     }
    804 
    805     void maybeRescheduleSync(SyncResult syncResult, SyncOperation operation) {
    806         boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG);
    807         if (isLoggable) {
    808             Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", " + operation);
    809         }
    810 
    811         operation = new SyncOperation(operation);
    812 
    813         // The SYNC_EXTRAS_IGNORE_BACKOFF only applies to the first attempt to sync a given
    814         // request. Retries of the request will always honor the backoff, so clear the
    815         // flag in case we retry this request.
    816         if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)) {
    817             operation.extras.remove(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
    818         }
    819 
    820         // If this sync aborted because the internal sync loop retried too many times then
    821         //   don't reschedule. Otherwise we risk getting into a retry loop.
    822         // If the operation succeeded to some extent then retry immediately.
    823         // If this was a two-way sync then retry soft errors with an exponential backoff.
    824         // If this was an upward sync then schedule a two-way sync immediately.
    825         // Otherwise do not reschedule.
    826         if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false)) {
    827             Log.d(TAG, "not retrying sync operation because SYNC_EXTRAS_DO_NOT_RETRY was specified "
    828                     + operation);
    829         } else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false)) {
    830             operation.extras.remove(ContentResolver.SYNC_EXTRAS_UPLOAD);
    831             Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
    832                     + "encountered an error: " + operation);
    833             scheduleSyncOperation(operation);
    834         } else if (syncResult.tooManyRetries) {
    835             Log.d(TAG, "not retrying sync operation because it retried too many times: "
    836                     + operation);
    837         } else if (syncResult.madeSomeProgress()) {
    838             if (isLoggable) {
    839                 Log.d(TAG, "retrying sync operation because even though it had an error "
    840                         + "it achieved some success");
    841             }
    842             scheduleSyncOperation(operation);
    843         } else if (syncResult.syncAlreadyInProgress) {
    844             if (isLoggable) {
    845                 Log.d(TAG, "retrying sync operation that failed because there was already a "
    846                         + "sync in progress: " + operation);
    847             }
    848             scheduleSyncOperation(new SyncOperation(operation.account, operation.syncSource,
    849                     operation.authority, operation.extras,
    850                     DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000));
    851         } else if (syncResult.hasSoftError()) {
    852             if (isLoggable) {
    853                 Log.d(TAG, "retrying sync operation because it encountered a soft error: "
    854                         + operation);
    855             }
    856             scheduleSyncOperation(operation);
    857         } else {
    858             Log.d(TAG, "not retrying sync operation because the error is a hard error: "
    859                     + operation);
    860         }
    861     }
    862 
    863     /**
    864      * @hide
    865      */
    866     class ActiveSyncContext extends ISyncContext.Stub implements ServiceConnection {
    867         final SyncOperation mSyncOperation;
    868         final long mHistoryRowId;
    869         ISyncAdapter mSyncAdapter;
    870         final long mStartTime;
    871         long mTimeoutStartTime;
    872         boolean mBound;
    873 
    874         public ActiveSyncContext(SyncOperation syncOperation,
    875                 long historyRowId) {
    876             super();
    877             mSyncOperation = syncOperation;
    878             mHistoryRowId = historyRowId;
    879             mSyncAdapter = null;
    880             mStartTime = SystemClock.elapsedRealtime();
    881             mTimeoutStartTime = mStartTime;
    882         }
    883 
    884         public void sendHeartbeat() {
    885             // heartbeats are no longer used
    886         }
    887 
    888         public void onFinished(SyncResult result) {
    889             // include "this" in the message so that the handler can ignore it if this
    890             // ActiveSyncContext is no longer the mActiveSyncContext at message handling
    891             // time
    892             sendSyncFinishedOrCanceledMessage(this, result);
    893         }
    894 
    895         public void toString(StringBuilder sb) {
    896             sb.append("startTime ").append(mStartTime)
    897                     .append(", mTimeoutStartTime ").append(mTimeoutStartTime)
    898                     .append(", mHistoryRowId ").append(mHistoryRowId)
    899                     .append(", syncOperation ").append(mSyncOperation);
    900         }
    901 
    902         public void onServiceConnected(ComponentName name, IBinder service) {
    903             Message msg = mSyncHandler.obtainMessage();
    904             msg.what = SyncHandler.MESSAGE_SERVICE_CONNECTED;
    905             msg.obj = new ServiceConnectionData(this, ISyncAdapter.Stub.asInterface(service));
    906             mSyncHandler.sendMessage(msg);
    907         }
    908 
    909         public void onServiceDisconnected(ComponentName name) {
    910             Message msg = mSyncHandler.obtainMessage();
    911             msg.what = SyncHandler.MESSAGE_SERVICE_DISCONNECTED;
    912             msg.obj = new ServiceConnectionData(this, null);
    913             mSyncHandler.sendMessage(msg);
    914         }
    915 
    916         boolean bindToSyncAdapter(RegisteredServicesCache.ServiceInfo info) {
    917             if (Log.isLoggable(TAG, Log.VERBOSE)) {
    918                 Log.d(TAG, "bindToSyncAdapter: " + info.componentName + ", connection " + this);
    919             }
    920             Intent intent = new Intent();
    921             intent.setAction("android.content.SyncAdapter");
    922             intent.setComponent(info.componentName);
    923             intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
    924                     com.android.internal.R.string.sync_binding_label);
    925             intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
    926                     mContext, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0));
    927             mBound = true;
    928             final boolean bindResult = mContext.bindService(intent, this,
    929                     Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND);
    930             if (!bindResult) {
    931                 mBound = false;
    932             }
    933             return bindResult;
    934         }
    935 
    936         protected void close() {
    937             if (Log.isLoggable(TAG, Log.VERBOSE)) {
    938                 Log.d(TAG, "unBindFromSyncAdapter: connection " + this);
    939             }
    940             if (mBound) {
    941                 mBound = false;
    942                 mContext.unbindService(this);
    943             }
    944         }
    945 
    946         @Override
    947         public String toString() {
    948             StringBuilder sb = new StringBuilder();
    949             toString(sb);
    950             return sb.toString();
    951         }
    952     }
    953 
    954     protected void dump(FileDescriptor fd, PrintWriter pw) {
    955         StringBuilder sb = new StringBuilder();
    956         dumpSyncState(pw, sb);
    957         dumpSyncHistory(pw, sb);
    958 
    959         pw.println();
    960         pw.println("SyncAdapters:");
    961         for (RegisteredServicesCache.ServiceInfo info : mSyncAdapters.getAllServices()) {
    962             pw.println("  " + info);
    963         }
    964     }
    965 
    966     static String formatTime(long time) {
    967         Time tobj = new Time();
    968         tobj.set(time);
    969         return tobj.format("%Y-%m-%d %H:%M:%S");
    970     }
    971 
    972     protected void dumpSyncState(PrintWriter pw, StringBuilder sb) {
    973         pw.print("data connected: "); pw.println(mDataConnectionIsConnected);
    974         pw.print("memory low: "); pw.println(mStorageIsLow);
    975 
    976         final Account[] accounts = mAccounts;
    977         pw.print("accounts: ");
    978         if (accounts != INITIAL_ACCOUNTS_ARRAY) {
    979             pw.println(accounts.length);
    980         } else {
    981             pw.println("not known yet");
    982         }
    983         final long now = SystemClock.elapsedRealtime();
    984         pw.print("now: "); pw.print(now);
    985         pw.println(" (" + formatTime(System.currentTimeMillis()) + ")");
    986         pw.print("uptime: "); pw.print(DateUtils.formatElapsedTime(now/1000));
    987                 pw.println(" (HH:MM:SS)");
    988         pw.print("time spent syncing: ");
    989                 pw.print(DateUtils.formatElapsedTime(
    990                         mSyncHandler.mSyncTimeTracker.timeSpentSyncing() / 1000));
    991                 pw.print(" (HH:MM:SS), sync ");
    992                 pw.print(mSyncHandler.mSyncTimeTracker.mLastWasSyncing ? "" : "not ");
    993                 pw.println("in progress");
    994         if (mSyncHandler.mAlarmScheduleTime != null) {
    995             pw.print("next alarm time: "); pw.print(mSyncHandler.mAlarmScheduleTime);
    996                     pw.print(" (");
    997                     pw.print(DateUtils.formatElapsedTime((mSyncHandler.mAlarmScheduleTime-now)/1000));
    998                     pw.println(" (HH:MM:SS) from now)");
    999         } else {
   1000             pw.println("no alarm is scheduled (there had better not be any pending syncs)");
   1001         }
   1002 
   1003         final SyncManager.ActiveSyncContext activeSyncContext = mActiveSyncContext;
   1004 
   1005         pw.print("active sync: "); pw.println(activeSyncContext);
   1006 
   1007         pw.print("notification info: ");
   1008         sb.setLength(0);
   1009         mSyncHandler.mSyncNotificationInfo.toString(sb);
   1010         pw.println(sb.toString());
   1011 
   1012         synchronized (mSyncQueue) {
   1013             pw.print("sync queue: ");
   1014             sb.setLength(0);
   1015             mSyncQueue.dump(sb);
   1016             pw.println(sb.toString());
   1017         }
   1018 
   1019         SyncInfo active = mSyncStorageEngine.getCurrentSync();
   1020         if (active != null) {
   1021             SyncStorageEngine.AuthorityInfo authority
   1022                     = mSyncStorageEngine.getAuthority(active.authorityId);
   1023             final long durationInSeconds = (now - active.startTime) / 1000;
   1024             pw.print("Active sync: ");
   1025                     pw.print(authority != null ? authority.account : "<no account>");
   1026                     pw.print(" ");
   1027                     pw.print(authority != null ? authority.authority : "<no account>");
   1028                     if (activeSyncContext != null) {
   1029                         pw.print(" ");
   1030                         pw.print(SyncStorageEngine.SOURCES[
   1031                                 activeSyncContext.mSyncOperation.syncSource]);
   1032                     }
   1033                     pw.print(", duration is ");
   1034                     pw.println(DateUtils.formatElapsedTime(durationInSeconds));
   1035         } else {
   1036             pw.println("No sync is in progress.");
   1037         }
   1038 
   1039         ArrayList<SyncStorageEngine.PendingOperation> ops
   1040                 = mSyncStorageEngine.getPendingOperations();
   1041         if (ops != null && ops.size() > 0) {
   1042             pw.println();
   1043             pw.println("Pending Syncs");
   1044             final int N = ops.size();
   1045             for (int i=0; i<N; i++) {
   1046                 SyncStorageEngine.PendingOperation op = ops.get(i);
   1047                 pw.print("  #"); pw.print(i); pw.print(": account=");
   1048                 pw.print(op.account.name); pw.print(":");
   1049                 pw.print(op.account.type); pw.print(" authority=");
   1050                 pw.print(op.authority); pw.print(" expedited=");
   1051                 pw.println(op.expedited);
   1052                 if (op.extras != null && op.extras.size() > 0) {
   1053                     sb.setLength(0);
   1054                     SyncOperation.extrasToStringBuilder(op.extras, sb, false /* asKey */);
   1055                     pw.print("    extras: "); pw.println(sb.toString());
   1056                 }
   1057             }
   1058         }
   1059 
   1060         // join the installed sync adapter with the accounts list and emit for everything
   1061         pw.println();
   1062         pw.println("Sync Status");
   1063         for (Account account : accounts) {
   1064             pw.print("  Account "); pw.print(account.name);
   1065                     pw.print(" "); pw.print(account.type);
   1066                     pw.println(":");
   1067             for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterType :
   1068                     mSyncAdapters.getAllServices()) {
   1069                 if (!syncAdapterType.type.accountType.equals(account.type)) {
   1070                     continue;
   1071                 }
   1072 
   1073                 SyncStorageEngine.AuthorityInfo settings = mSyncStorageEngine.getOrCreateAuthority(
   1074                         account, syncAdapterType.type.authority);
   1075                 SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(settings);
   1076                 pw.print("    "); pw.print(settings.authority);
   1077                 pw.println(":");
   1078                 pw.print("      settings:");
   1079                 pw.print(" " + (settings.syncable > 0
   1080                         ? "syncable"
   1081                         : (settings.syncable == 0 ? "not syncable" : "not initialized")));
   1082                 pw.print(", " + (settings.enabled ? "enabled" : "disabled"));
   1083                 if (settings.delayUntil > now) {
   1084                     pw.print(", delay for "
   1085                             + ((settings.delayUntil - now) / 1000) + " sec");
   1086                 }
   1087                 if (settings.backoffTime > now) {
   1088                     pw.print(", backoff for "
   1089                             + ((settings.backoffTime - now) / 1000) + " sec");
   1090                 }
   1091                 if (settings.backoffDelay > 0) {
   1092                     pw.print(", the backoff increment is " + settings.backoffDelay / 1000
   1093                                 + " sec");
   1094                 }
   1095                 pw.println();
   1096                 for (int periodicIndex = 0;
   1097                         periodicIndex < settings.periodicSyncs.size();
   1098                         periodicIndex++) {
   1099                     Pair<Bundle, Long> info = settings.periodicSyncs.get(periodicIndex);
   1100                     long lastPeriodicTime = status.getPeriodicSyncTime(periodicIndex);
   1101                     long nextPeriodicTime = lastPeriodicTime + info.second * 1000;
   1102                     pw.println("      periodic period=" + info.second
   1103                             + ", extras=" + info.first
   1104                             + ", next=" + formatTime(nextPeriodicTime));
   1105                 }
   1106                 pw.print("      count: local="); pw.print(status.numSourceLocal);
   1107                 pw.print(" poll="); pw.print(status.numSourcePoll);
   1108                 pw.print(" periodic="); pw.print(status.numSourcePeriodic);
   1109                 pw.print(" server="); pw.print(status.numSourceServer);
   1110                 pw.print(" user="); pw.print(status.numSourceUser);
   1111                 pw.print(" total="); pw.print(status.numSyncs);
   1112                 pw.println();
   1113                 pw.print("      total duration: ");
   1114                 pw.println(DateUtils.formatElapsedTime(status.totalElapsedTime/1000));
   1115                 if (status.lastSuccessTime != 0) {
   1116                     pw.print("      SUCCESS: source=");
   1117                     pw.print(SyncStorageEngine.SOURCES[status.lastSuccessSource]);
   1118                     pw.print(" time=");
   1119                     pw.println(formatTime(status.lastSuccessTime));
   1120                 }
   1121                 if (status.lastFailureTime != 0) {
   1122                     pw.print("      FAILURE: source=");
   1123                     pw.print(SyncStorageEngine.SOURCES[
   1124                             status.lastFailureSource]);
   1125                     pw.print(" initialTime=");
   1126                     pw.print(formatTime(status.initialFailureTime));
   1127                     pw.print(" lastTime=");
   1128                     pw.println(formatTime(status.lastFailureTime));
   1129                     pw.print("      message: "); pw.println(status.lastFailureMesg);
   1130                 }
   1131             }
   1132         }
   1133     }
   1134 
   1135     private void dumpTimeSec(PrintWriter pw, long time) {
   1136         pw.print(time/1000); pw.print('.'); pw.print((time/100)%10);
   1137         pw.print('s');
   1138     }
   1139 
   1140     private void dumpDayStatistic(PrintWriter pw, SyncStorageEngine.DayStats ds) {
   1141         pw.print("Success ("); pw.print(ds.successCount);
   1142         if (ds.successCount > 0) {
   1143             pw.print(" for "); dumpTimeSec(pw, ds.successTime);
   1144             pw.print(" avg="); dumpTimeSec(pw, ds.successTime/ds.successCount);
   1145         }
   1146         pw.print(") Failure ("); pw.print(ds.failureCount);
   1147         if (ds.failureCount > 0) {
   1148             pw.print(" for "); dumpTimeSec(pw, ds.failureTime);
   1149             pw.print(" avg="); dumpTimeSec(pw, ds.failureTime/ds.failureCount);
   1150         }
   1151         pw.println(")");
   1152     }
   1153 
   1154     protected void dumpSyncHistory(PrintWriter pw, StringBuilder sb) {
   1155         SyncStorageEngine.DayStats dses[] = mSyncStorageEngine.getDayStatistics();
   1156         if (dses != null && dses[0] != null) {
   1157             pw.println();
   1158             pw.println("Sync Statistics");
   1159             pw.print("  Today:  "); dumpDayStatistic(pw, dses[0]);
   1160             int today = dses[0].day;
   1161             int i;
   1162             SyncStorageEngine.DayStats ds;
   1163 
   1164             // Print each day in the current week.
   1165             for (i=1; i<=6 && i < dses.length; i++) {
   1166                 ds = dses[i];
   1167                 if (ds == null) break;
   1168                 int delta = today-ds.day;
   1169                 if (delta > 6) break;
   1170 
   1171                 pw.print("  Day-"); pw.print(delta); pw.print(":  ");
   1172                 dumpDayStatistic(pw, ds);
   1173             }
   1174 
   1175             // Aggregate all following days into weeks and print totals.
   1176             int weekDay = today;
   1177             while (i < dses.length) {
   1178                 SyncStorageEngine.DayStats aggr = null;
   1179                 weekDay -= 7;
   1180                 while (i < dses.length) {
   1181                     ds = dses[i];
   1182                     if (ds == null) {
   1183                         i = dses.length;
   1184                         break;
   1185                     }
   1186                     int delta = weekDay-ds.day;
   1187                     if (delta > 6) break;
   1188                     i++;
   1189 
   1190                     if (aggr == null) {
   1191                         aggr = new SyncStorageEngine.DayStats(weekDay);
   1192                     }
   1193                     aggr.successCount += ds.successCount;
   1194                     aggr.successTime += ds.successTime;
   1195                     aggr.failureCount += ds.failureCount;
   1196                     aggr.failureTime += ds.failureTime;
   1197                 }
   1198                 if (aggr != null) {
   1199                     pw.print("  Week-"); pw.print((today-weekDay)/7); pw.print(": ");
   1200                     dumpDayStatistic(pw, aggr);
   1201                 }
   1202             }
   1203         }
   1204 
   1205         ArrayList<SyncStorageEngine.SyncHistoryItem> items
   1206                 = mSyncStorageEngine.getSyncHistory();
   1207         if (items != null && items.size() > 0) {
   1208             pw.println();
   1209             pw.println("Recent Sync History");
   1210             final int N = items.size();
   1211             for (int i=0; i<N; i++) {
   1212                 SyncStorageEngine.SyncHistoryItem item = items.get(i);
   1213                 SyncStorageEngine.AuthorityInfo authority
   1214                         = mSyncStorageEngine.getAuthority(item.authorityId);
   1215                 pw.print("  #"); pw.print(i+1); pw.print(": ");
   1216                         if (authority != null) {
   1217                             pw.print(authority.account.name);
   1218                             pw.print(":");
   1219                             pw.print(authority.account.type);
   1220                             pw.print(" ");
   1221                             pw.print(authority.authority);
   1222                         } else {
   1223                             pw.print("<no account>");
   1224                         }
   1225                 Time time = new Time();
   1226                 time.set(item.eventTime);
   1227                 pw.print(" "); pw.print(SyncStorageEngine.SOURCES[item.source]);
   1228                         pw.print(" @ ");
   1229                         pw.print(formatTime(item.eventTime));
   1230                         pw.print(" for ");
   1231                         dumpTimeSec(pw, item.elapsedTime);
   1232                         pw.println();
   1233                 if (item.event != SyncStorageEngine.EVENT_STOP
   1234                         || item.upstreamActivity !=0
   1235                         || item.downstreamActivity != 0) {
   1236                     pw.print("    event="); pw.print(item.event);
   1237                             pw.print(" upstreamActivity="); pw.print(item.upstreamActivity);
   1238                             pw.print(" downstreamActivity="); pw.println(item.downstreamActivity);
   1239                 }
   1240                 if (item.mesg != null
   1241                         && !SyncStorageEngine.MESG_SUCCESS.equals(item.mesg)) {
   1242                     pw.print("    mesg="); pw.println(item.mesg);
   1243                 }
   1244             }
   1245         }
   1246     }
   1247 
   1248     /**
   1249      * A helper object to keep track of the time we have spent syncing since the last boot
   1250      */
   1251     private class SyncTimeTracker {
   1252         /** True if a sync was in progress on the most recent call to update() */
   1253         boolean mLastWasSyncing = false;
   1254         /** Used to track when lastWasSyncing was last set */
   1255         long mWhenSyncStarted = 0;
   1256         /** The cumulative time we have spent syncing */
   1257         private long mTimeSpentSyncing;
   1258 
   1259         /** Call to let the tracker know that the sync state may have changed */
   1260         public synchronized void update() {
   1261             final boolean isSyncInProgress = mActiveSyncContext != null;
   1262             if (isSyncInProgress == mLastWasSyncing) return;
   1263             final long now = SystemClock.elapsedRealtime();
   1264             if (isSyncInProgress) {
   1265                 mWhenSyncStarted = now;
   1266             } else {
   1267                 mTimeSpentSyncing += now - mWhenSyncStarted;
   1268             }
   1269             mLastWasSyncing = isSyncInProgress;
   1270         }
   1271 
   1272         /** Get how long we have been syncing, in ms */
   1273         public synchronized long timeSpentSyncing() {
   1274             if (!mLastWasSyncing) return mTimeSpentSyncing;
   1275 
   1276             final long now = SystemClock.elapsedRealtime();
   1277             return mTimeSpentSyncing + (now - mWhenSyncStarted);
   1278         }
   1279     }
   1280 
   1281     class ServiceConnectionData {
   1282         public final ActiveSyncContext activeSyncContext;
   1283         public final ISyncAdapter syncAdapter;
   1284         ServiceConnectionData(ActiveSyncContext activeSyncContext, ISyncAdapter syncAdapter) {
   1285             this.activeSyncContext = activeSyncContext;
   1286             this.syncAdapter = syncAdapter;
   1287         }
   1288     }
   1289 
   1290     /**
   1291      * Handles SyncOperation Messages that are posted to the associated
   1292      * HandlerThread.
   1293      */
   1294     class SyncHandler extends Handler {
   1295         // Messages that can be sent on mHandler
   1296         private static final int MESSAGE_SYNC_FINISHED = 1;
   1297         private static final int MESSAGE_SYNC_ALARM = 2;
   1298         private static final int MESSAGE_CHECK_ALARMS = 3;
   1299         private static final int MESSAGE_SERVICE_CONNECTED = 4;
   1300         private static final int MESSAGE_SERVICE_DISCONNECTED = 5;
   1301 
   1302         public final SyncNotificationInfo mSyncNotificationInfo = new SyncNotificationInfo();
   1303         private Long mAlarmScheduleTime = null;
   1304         public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker();
   1305 
   1306         // used to track if we have installed the error notification so that we don't reinstall
   1307         // it if sync is still failing
   1308         private boolean mErrorNotificationInstalled = false;
   1309         private volatile CountDownLatch mReadyToRunLatch = new CountDownLatch(1);
   1310         public void onBootCompleted() {
   1311             mBootCompleted = true;
   1312             mSyncStorageEngine.doDatabaseCleanup(AccountManager.get(mContext).getAccounts());
   1313             if (mReadyToRunLatch != null) {
   1314                 mReadyToRunLatch.countDown();
   1315             }
   1316         }
   1317 
   1318         private void waitUntilReadyToRun() {
   1319             CountDownLatch latch = mReadyToRunLatch;
   1320             if (latch != null) {
   1321                 while (true) {
   1322                     try {
   1323                         latch.await();
   1324                         mReadyToRunLatch = null;
   1325                         return;
   1326                     } catch (InterruptedException e) {
   1327                         Thread.currentThread().interrupt();
   1328                     }
   1329                 }
   1330             }
   1331         }
   1332         /**
   1333          * Used to keep track of whether a sync notification is active and who it is for.
   1334          */
   1335         class SyncNotificationInfo {
   1336             // only valid if isActive is true
   1337             public Account account;
   1338 
   1339             // only valid if isActive is true
   1340             public String authority;
   1341 
   1342             // true iff the notification manager has been asked to send the notification
   1343             public boolean isActive = false;
   1344 
   1345             // Set when we transition from not running a sync to running a sync, and cleared on
   1346             // the opposite transition.
   1347             public Long startTime = null;
   1348 
   1349             public void toString(StringBuilder sb) {
   1350                 sb.append("account ").append(account)
   1351                         .append(", authority ").append(authority)
   1352                         .append(", isActive ").append(isActive)
   1353                         .append(", startTime ").append(startTime);
   1354             }
   1355 
   1356             @Override
   1357             public String toString() {
   1358                 StringBuilder sb = new StringBuilder();
   1359                 toString(sb);
   1360                 return sb.toString();
   1361             }
   1362         }
   1363 
   1364         public SyncHandler(Looper looper) {
   1365             super(looper);
   1366         }
   1367 
   1368         public void handleMessage(Message msg) {
   1369             Long earliestFuturePollTime = null;
   1370             try {
   1371                 waitUntilReadyToRun();
   1372                 // Always do this first so that we be sure that any periodic syncs that
   1373                 // are ready to run have been converted into pending syncs. This allows the
   1374                 // logic that considers the next steps to take based on the set of pending syncs
   1375                 // to also take into account the periodic syncs.
   1376                 earliestFuturePollTime = scheduleReadyPeriodicSyncs();
   1377                 switch (msg.what) {
   1378                     case SyncHandler.MESSAGE_SYNC_FINISHED:
   1379                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1380                             Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_FINISHED");
   1381                         }
   1382                         SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload)msg.obj;
   1383                         if (mActiveSyncContext != payload.activeSyncContext) {
   1384                             Log.d(TAG, "handleSyncHandlerMessage: sync context doesn't match, "
   1385                                     + "dropping: mActiveSyncContext " + mActiveSyncContext
   1386                                     + " != " + payload.activeSyncContext);
   1387                             return;
   1388                         }
   1389                         runSyncFinishedOrCanceled(payload.syncResult);
   1390 
   1391                         // since we are no longer syncing, check if it is time to start a new sync
   1392                         runStateIdle();
   1393                         break;
   1394 
   1395                     case SyncHandler.MESSAGE_SERVICE_CONNECTED: {
   1396                         ServiceConnectionData msgData = (ServiceConnectionData)msg.obj;
   1397                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1398                             Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CONNECTED: "
   1399                                     + msgData.activeSyncContext
   1400                                     + " active is " + mActiveSyncContext);
   1401                         }
   1402                         // check that this isn't an old message
   1403                         if (mActiveSyncContext == msgData.activeSyncContext) {
   1404                             runBoundToSyncAdapter(msgData.syncAdapter);
   1405                         }
   1406                         break;
   1407                     }
   1408 
   1409                     case SyncHandler.MESSAGE_SERVICE_DISCONNECTED: {
   1410                         ServiceConnectionData msgData = (ServiceConnectionData)msg.obj;
   1411                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1412                             Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_DISCONNECTED: "
   1413                                     + msgData.activeSyncContext
   1414                                     + " active is " + mActiveSyncContext);
   1415                         }
   1416                         // check that this isn't an old message
   1417                         if (mActiveSyncContext == msgData.activeSyncContext) {
   1418                             // cancel the sync if we have a syncadapter, which means one is
   1419                             // outstanding
   1420                             if (mActiveSyncContext.mSyncAdapter != null) {
   1421                                 try {
   1422                                     mActiveSyncContext.mSyncAdapter.cancelSync(mActiveSyncContext);
   1423                                 } catch (RemoteException e) {
   1424                                     // we don't need to retry this in this case
   1425                                 }
   1426                             }
   1427 
   1428                             // pretend that the sync failed with an IOException,
   1429                             // which is a soft error
   1430                             SyncResult syncResult = new SyncResult();
   1431                             syncResult.stats.numIoExceptions++;
   1432                             runSyncFinishedOrCanceled(syncResult);
   1433 
   1434                             // since we are no longer syncing, check if it is time to start a new
   1435                             // sync
   1436                             runStateIdle();
   1437                         }
   1438 
   1439                         break;
   1440                     }
   1441 
   1442                     case SyncHandler.MESSAGE_SYNC_ALARM: {
   1443                         boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
   1444                         if (isLoggable) {
   1445                             Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_ALARM");
   1446                         }
   1447                         mAlarmScheduleTime = null;
   1448                         try {
   1449                             if (mActiveSyncContext != null) {
   1450                                 if (isLoggable) {
   1451                                     Log.v(TAG, "handleSyncHandlerMessage: sync context is active");
   1452                                 }
   1453                                 runStateSyncing();
   1454                             }
   1455 
   1456                             // if the above call to runStateSyncing() resulted in the end of a sync,
   1457                             // check if it is time to start a new sync
   1458                             if (mActiveSyncContext == null) {
   1459                                 if (isLoggable) {
   1460                                     Log.v(TAG, "handleSyncHandlerMessage: "
   1461                                             + "sync context is not active");
   1462                                 }
   1463                                 runStateIdle();
   1464                             }
   1465                         } finally {
   1466                             mHandleAlarmWakeLock.release();
   1467                         }
   1468                         break;
   1469                     }
   1470 
   1471                     case SyncHandler.MESSAGE_CHECK_ALARMS:
   1472                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1473                             Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_CHECK_ALARMS");
   1474                         }
   1475                         // we do all the work for this case in the finally block
   1476                         break;
   1477                 }
   1478             } finally {
   1479                 final boolean isSyncInProgress = mActiveSyncContext != null;
   1480                 if (!isSyncInProgress) {
   1481                     mSyncWakeLock.release();
   1482                 }
   1483                 manageSyncNotification();
   1484                 manageErrorNotification();
   1485                 manageSyncAlarm(earliestFuturePollTime);
   1486                 mSyncTimeTracker.update();
   1487             }
   1488         }
   1489 
   1490         /**
   1491          * Turn any periodic sync operations that are ready to run into pending sync operations.
   1492          * @return the desired start time of the earliest future  periodic sync operation,
   1493          * in milliseconds since boot
   1494          */
   1495         private Long scheduleReadyPeriodicSyncs() {
   1496             final boolean backgroundDataUsageAllowed =
   1497                     getConnectivityManager().getBackgroundDataSetting();
   1498             Long earliestFuturePollTime = null;
   1499             if (!backgroundDataUsageAllowed || !mSyncStorageEngine.getMasterSyncAutomatically()) {
   1500                 return earliestFuturePollTime;
   1501             }
   1502             final long nowAbsolute = System.currentTimeMillis();
   1503             ArrayList<SyncStorageEngine.AuthorityInfo> infos = mSyncStorageEngine.getAuthorities();
   1504             for (SyncStorageEngine.AuthorityInfo info : infos) {
   1505                 // skip the sync if the account of this operation no longer exists
   1506                 if (!ArrayUtils.contains(mAccounts, info.account)) {
   1507                     continue;
   1508                 }
   1509 
   1510                 if (!mSyncStorageEngine.getSyncAutomatically(info.account, info.authority)) {
   1511                     continue;
   1512                 }
   1513 
   1514                 if (mSyncStorageEngine.getIsSyncable(info.account, info.authority) == 0) {
   1515                     continue;
   1516                 }
   1517 
   1518                 SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(info);
   1519                 for (int i = 0, N = info.periodicSyncs.size(); i < N; i++) {
   1520                     final Bundle extras = info.periodicSyncs.get(i).first;
   1521                     final Long periodInSeconds = info.periodicSyncs.get(i).second;
   1522                     // find when this periodic sync was last scheduled to run
   1523                     final long lastPollTimeAbsolute = status.getPeriodicSyncTime(i);
   1524                     // compute when this periodic sync should next run
   1525                     long nextPollTimeAbsolute = lastPollTimeAbsolute + periodInSeconds * 1000;
   1526                     // if it is ready to run then schedule it and mark it as having been scheduled
   1527                     if (nextPollTimeAbsolute <= nowAbsolute) {
   1528                         scheduleSyncOperation(
   1529                                 new SyncOperation(info.account, SyncStorageEngine.SOURCE_PERIODIC,
   1530                                         info.authority, extras, 0 /* delay */));
   1531                         status.setPeriodicSyncTime(i, nowAbsolute);
   1532                     } else {
   1533                         // it isn't ready to run, remember this time if it is earlier than
   1534                         // earliestFuturePollTime
   1535                         if (earliestFuturePollTime == null
   1536                                 || nextPollTimeAbsolute < earliestFuturePollTime) {
   1537                             earliestFuturePollTime = nextPollTimeAbsolute;
   1538                         }
   1539                     }
   1540                 }
   1541             }
   1542 
   1543             if (earliestFuturePollTime == null) {
   1544                 return null;
   1545             }
   1546 
   1547             // convert absolute time to elapsed time
   1548             return SystemClock.elapsedRealtime()
   1549                     + ((earliestFuturePollTime < nowAbsolute)
   1550                       ? 0
   1551                       : (earliestFuturePollTime - nowAbsolute));
   1552         }
   1553 
   1554         private void runStateSyncing() {
   1555             // if the sync timeout has been reached then cancel it
   1556             ActiveSyncContext activeSyncContext = mActiveSyncContext;
   1557 
   1558             final long now = SystemClock.elapsedRealtime();
   1559             if (now > activeSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC) {
   1560                 Pair<SyncOperation, Long> nextOpAndRunTime;
   1561                 synchronized (mSyncQueue) {
   1562                     nextOpAndRunTime = mSyncQueue.nextOperation();
   1563                 }
   1564                 if (nextOpAndRunTime != null && nextOpAndRunTime.second <= now) {
   1565                     Log.d(TAG, "canceling and rescheduling sync because it ran too long: "
   1566                             + activeSyncContext.mSyncOperation);
   1567                     scheduleSyncOperation(new SyncOperation(activeSyncContext.mSyncOperation));
   1568                     sendSyncFinishedOrCanceledMessage(activeSyncContext,
   1569                             null /* no result since this is a cancel */);
   1570                 } else {
   1571                     activeSyncContext.mTimeoutStartTime = now + MAX_TIME_PER_SYNC;
   1572                 }
   1573             }
   1574 
   1575             // no need to schedule an alarm, as that will be done by our caller.
   1576         }
   1577 
   1578         private void runStateIdle() {
   1579             boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
   1580             if (isLoggable) Log.v(TAG, "runStateIdle");
   1581 
   1582             // If we aren't ready to run (e.g. the data connection is down), get out.
   1583             if (!mDataConnectionIsConnected) {
   1584                 if (isLoggable) {
   1585                     Log.v(TAG, "runStateIdle: no data connection, skipping");
   1586                 }
   1587                 return;
   1588             }
   1589 
   1590             if (mStorageIsLow) {
   1591                 if (isLoggable) {
   1592                     Log.v(TAG, "runStateIdle: memory low, skipping");
   1593                 }
   1594                 return;
   1595             }
   1596 
   1597             // If the accounts aren't known yet then we aren't ready to run. We will be kicked
   1598             // when the account lookup request does complete.
   1599             Account[] accounts = mAccounts;
   1600             if (accounts == INITIAL_ACCOUNTS_ARRAY) {
   1601                 if (isLoggable) {
   1602                     Log.v(TAG, "runStateIdle: accounts not known, skipping");
   1603                 }
   1604                 return;
   1605             }
   1606 
   1607             // Otherwise consume SyncOperations from the head of the SyncQueue until one is
   1608             // found that is runnable (not disabled, etc). If that one is ready to run then
   1609             // start it, otherwise just get out.
   1610             SyncOperation op;
   1611             int syncableState;
   1612             final boolean backgroundDataUsageAllowed =
   1613                     getConnectivityManager().getBackgroundDataSetting();
   1614             final boolean masterSyncAutomatically = mSyncStorageEngine.getMasterSyncAutomatically();
   1615 
   1616             synchronized (mSyncQueue) {
   1617                 final long now = SystemClock.elapsedRealtime();
   1618                 while (true) {
   1619                     Pair<SyncOperation, Long> nextOpAndRunTime = mSyncQueue.nextOperation();
   1620                     if (nextOpAndRunTime == null || nextOpAndRunTime.second > now) {
   1621                         if (isLoggable) {
   1622                             Log.v(TAG, "runStateIdle: no more ready sync operations, returning");
   1623                         }
   1624                         return;
   1625                     }
   1626                     op = nextOpAndRunTime.first;
   1627 
   1628                     // we are either going to run this sync or drop it so go ahead and
   1629                     // remove it from the queue now
   1630                     mSyncQueue.remove(op);
   1631 
   1632                     // drop the sync if the account of this operation no longer exists
   1633                     if (!ArrayUtils.contains(mAccounts, op.account)) {
   1634                         continue;
   1635                     }
   1636 
   1637 
   1638                     // drop this sync request if it isn't syncable, intializing the sync adapter
   1639                     // if the syncable state is set to "unknown"
   1640                     syncableState = mSyncStorageEngine.getIsSyncable(op.account, op.authority);
   1641                     if (syncableState == 0) {
   1642                         continue;
   1643                     }
   1644 
   1645                     // skip the sync if it isn't manual and auto sync or
   1646                     // background data usage is disabled
   1647                     if (!op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false)
   1648                             && (syncableState > 0)
   1649                             && (!masterSyncAutomatically
   1650                                 || !backgroundDataUsageAllowed
   1651                                 || !mSyncStorageEngine.getSyncAutomatically(
   1652                                        op.account, op.authority))) {
   1653                         continue;
   1654                     }
   1655 
   1656                     // go ahead and try to sync this syncOperation
   1657                     break;
   1658                 }
   1659 
   1660                 // We will do this sync. Run it outside of the synchronized block.
   1661                 if (isLoggable) {
   1662                     Log.v(TAG, "runStateIdle: we are going to sync " + op);
   1663                 }
   1664             }
   1665 
   1666             // convert the op into an initialization sync if the syncable state is "unknown" and
   1667             // op isn't already an initialization sync. If it is marked syncable then convert
   1668             // this into a regular sync
   1669             final boolean initializeIsSet =
   1670                     op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
   1671             if (syncableState < 0 && !initializeIsSet) {
   1672                 op.extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
   1673                 op = new SyncOperation(op);
   1674             } else if (syncableState > 0 && initializeIsSet) {
   1675                 op.extras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
   1676                 op = new SyncOperation(op);
   1677             }
   1678 
   1679             // connect to the sync adapter
   1680             SyncAdapterType syncAdapterType = SyncAdapterType.newKey(op.authority, op.account.type);
   1681             RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
   1682                     mSyncAdapters.getServiceInfo(syncAdapterType);
   1683             if (syncAdapterInfo == null) {
   1684                 Log.d(TAG, "can't find a sync adapter for " + syncAdapterType
   1685                         + ", removing settings for it");
   1686                 mSyncStorageEngine.removeAuthority(op.account, op.authority);
   1687                 runStateIdle();
   1688                 return;
   1689             }
   1690 
   1691             ActiveSyncContext activeSyncContext =
   1692                     new ActiveSyncContext(op, insertStartSyncEvent(op));
   1693             mActiveSyncContext = activeSyncContext;
   1694             if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1695                 Log.v(TAG, "runStateIdle: setting mActiveSyncContext to " + mActiveSyncContext);
   1696             }
   1697             mSyncStorageEngine.setActiveSync(mActiveSyncContext);
   1698             if (!activeSyncContext.bindToSyncAdapter(syncAdapterInfo)) {
   1699                 Log.e(TAG, "Bind attempt failed to " + syncAdapterInfo);
   1700                 mActiveSyncContext.close();
   1701                 mActiveSyncContext = null;
   1702                 mSyncStorageEngine.setActiveSync(mActiveSyncContext);
   1703                 runStateIdle();
   1704                 return;
   1705             }
   1706 
   1707             mSyncWakeLock.acquire();
   1708             // no need to schedule an alarm, as that will be done by our caller.
   1709 
   1710             // the next step will occur when we get either a timeout or a
   1711             // MESSAGE_SERVICE_CONNECTED or MESSAGE_SERVICE_DISCONNECTED message
   1712         }
   1713 
   1714         private void runBoundToSyncAdapter(ISyncAdapter syncAdapter) {
   1715             mActiveSyncContext.mSyncAdapter = syncAdapter;
   1716             final SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
   1717             try {
   1718                 syncAdapter.startSync(mActiveSyncContext, syncOperation.authority,
   1719                         syncOperation.account, syncOperation.extras);
   1720             } catch (RemoteException remoteExc) {
   1721                 Log.d(TAG, "runStateIdle: caught a RemoteException, rescheduling", remoteExc);
   1722                 mActiveSyncContext.close();
   1723                 mActiveSyncContext = null;
   1724                 mSyncStorageEngine.setActiveSync(mActiveSyncContext);
   1725                 increaseBackoffSetting(syncOperation);
   1726                 scheduleSyncOperation(new SyncOperation(syncOperation));
   1727             } catch (RuntimeException exc) {
   1728                 mActiveSyncContext.close();
   1729                 mActiveSyncContext = null;
   1730                 mSyncStorageEngine.setActiveSync(mActiveSyncContext);
   1731                 Log.e(TAG, "Caught RuntimeException while starting the sync " + syncOperation, exc);
   1732             }
   1733         }
   1734 
   1735         private void runSyncFinishedOrCanceled(SyncResult syncResult) {
   1736             boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
   1737             final ActiveSyncContext activeSyncContext = mActiveSyncContext;
   1738             mActiveSyncContext = null;
   1739             mSyncStorageEngine.setActiveSync(mActiveSyncContext);
   1740 
   1741             final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
   1742 
   1743             final long elapsedTime = SystemClock.elapsedRealtime() - activeSyncContext.mStartTime;
   1744 
   1745             String historyMessage;
   1746             int downstreamActivity;
   1747             int upstreamActivity;
   1748             if (syncResult != null) {
   1749                 if (isLoggable) {
   1750                     Log.v(TAG, "runSyncFinishedOrCanceled [finished]: "
   1751                             + syncOperation + ", result " + syncResult);
   1752                 }
   1753 
   1754                 if (!syncResult.hasError()) {
   1755                     historyMessage = SyncStorageEngine.MESG_SUCCESS;
   1756                     // TODO: set these correctly when the SyncResult is extended to include it
   1757                     downstreamActivity = 0;
   1758                     upstreamActivity = 0;
   1759                     clearBackoffSetting(syncOperation);
   1760                     // if this was an initialization sync and the sync adapter is now
   1761                     // marked syncable then reschedule the sync. The next time it runs it
   1762                     // will be made into a regular sync.
   1763                     if (syncOperation.extras.getBoolean(
   1764                                 ContentResolver.SYNC_EXTRAS_INITIALIZE, false)
   1765                             && mSyncStorageEngine.getIsSyncable(
   1766                                 syncOperation.account, syncOperation.authority) > 0) {
   1767                         scheduleSyncOperation(new SyncOperation(syncOperation));
   1768                     }
   1769                 } else {
   1770                     Log.d(TAG, "failed sync operation " + syncOperation + ", " + syncResult);
   1771                     // the operation failed so increase the backoff time
   1772                     if (!syncResult.syncAlreadyInProgress) {
   1773                         increaseBackoffSetting(syncOperation);
   1774                     }
   1775                     // reschedule the sync if so indicated by the syncResult
   1776                     maybeRescheduleSync(syncResult, syncOperation);
   1777                     historyMessage = Integer.toString(syncResultToErrorNumber(syncResult));
   1778                     // TODO: set these correctly when the SyncResult is extended to include it
   1779                     downstreamActivity = 0;
   1780                     upstreamActivity = 0;
   1781                 }
   1782 
   1783                 setDelayUntilTime(syncOperation, syncResult.delayUntil);
   1784             } else {
   1785                 if (isLoggable) {
   1786                     Log.v(TAG, "runSyncFinishedOrCanceled [canceled]: " + syncOperation);
   1787                 }
   1788                 if (activeSyncContext.mSyncAdapter != null) {
   1789                     try {
   1790                         activeSyncContext.mSyncAdapter.cancelSync(activeSyncContext);
   1791                     } catch (RemoteException e) {
   1792                         // we don't need to retry this in this case
   1793                     }
   1794                 }
   1795                 historyMessage = SyncStorageEngine.MESG_CANCELED;
   1796                 downstreamActivity = 0;
   1797                 upstreamActivity = 0;
   1798             }
   1799 
   1800             stopSyncEvent(activeSyncContext.mHistoryRowId, syncOperation, historyMessage,
   1801                     upstreamActivity, downstreamActivity, elapsedTime);
   1802 
   1803             activeSyncContext.close();
   1804 
   1805             if (syncResult != null && syncResult.tooManyDeletions) {
   1806                 installHandleTooManyDeletesNotification(syncOperation.account,
   1807                         syncOperation.authority, syncResult.stats.numDeletes);
   1808             } else {
   1809                 mNotificationMgr.cancel(
   1810                         syncOperation.account.hashCode() ^ syncOperation.authority.hashCode());
   1811             }
   1812 
   1813             if (syncResult != null && syncResult.fullSyncRequested) {
   1814                 scheduleSyncOperation(new SyncOperation(syncOperation.account,
   1815                         syncOperation.syncSource, syncOperation.authority, new Bundle(), 0));
   1816             }
   1817             // no need to schedule an alarm, as that will be done by our caller.
   1818         }
   1819 
   1820         /**
   1821          * Convert the error-containing SyncResult into the Sync.History error number. Since
   1822          * the SyncResult may indicate multiple errors at once, this method just returns the
   1823          * most "serious" error.
   1824          * @param syncResult the SyncResult from which to read
   1825          * @return the most "serious" error set in the SyncResult
   1826          * @throws IllegalStateException if the SyncResult does not indicate any errors.
   1827          *   If SyncResult.error() is true then it is safe to call this.
   1828          */
   1829         private int syncResultToErrorNumber(SyncResult syncResult) {
   1830             if (syncResult.syncAlreadyInProgress)
   1831                 return ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS;
   1832             if (syncResult.stats.numAuthExceptions > 0)
   1833                 return ContentResolver.SYNC_ERROR_AUTHENTICATION;
   1834             if (syncResult.stats.numIoExceptions > 0)
   1835                 return ContentResolver.SYNC_ERROR_IO;
   1836             if (syncResult.stats.numParseExceptions > 0)
   1837                 return ContentResolver.SYNC_ERROR_PARSE;
   1838             if (syncResult.stats.numConflictDetectedExceptions > 0)
   1839                 return ContentResolver.SYNC_ERROR_CONFLICT;
   1840             if (syncResult.tooManyDeletions)
   1841                 return ContentResolver.SYNC_ERROR_TOO_MANY_DELETIONS;
   1842             if (syncResult.tooManyRetries)
   1843                 return ContentResolver.SYNC_ERROR_TOO_MANY_RETRIES;
   1844             if (syncResult.databaseError)
   1845                 return ContentResolver.SYNC_ERROR_INTERNAL;
   1846             throw new IllegalStateException("we are not in an error state, " + syncResult);
   1847         }
   1848 
   1849         private void manageSyncNotification() {
   1850             boolean shouldCancel;
   1851             boolean shouldInstall;
   1852 
   1853             if (mActiveSyncContext == null) {
   1854                 mSyncNotificationInfo.startTime = null;
   1855 
   1856                 // we aren't syncing. if the notification is active then remember that we need
   1857                 // to cancel it and then clear out the info
   1858                 shouldCancel = mSyncNotificationInfo.isActive;
   1859                 shouldInstall = false;
   1860             } else {
   1861                 // we are syncing
   1862                 final SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
   1863 
   1864                 final long now = SystemClock.elapsedRealtime();
   1865                 if (mSyncNotificationInfo.startTime == null) {
   1866                     mSyncNotificationInfo.startTime = now;
   1867                 }
   1868 
   1869                 // cancel the notification if it is up and the authority or account is wrong
   1870                 shouldCancel = mSyncNotificationInfo.isActive &&
   1871                         (!syncOperation.authority.equals(mSyncNotificationInfo.authority)
   1872                         || !syncOperation.account.equals(mSyncNotificationInfo.account));
   1873 
   1874                 // there are four cases:
   1875                 // - the notification is up and there is no change: do nothing
   1876                 // - the notification is up but we should cancel since it is stale:
   1877                 //   need to install
   1878                 // - the notification is not up but it isn't time yet: don't install
   1879                 // - the notification is not up and it is time: need to install
   1880 
   1881                 if (mSyncNotificationInfo.isActive) {
   1882                     shouldInstall = shouldCancel;
   1883                 } else {
   1884                     final boolean timeToShowNotification =
   1885                             now > mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY;
   1886                     // show the notification immediately if this is a manual sync
   1887                     final boolean manualSync = syncOperation.extras
   1888                             .getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
   1889                     shouldInstall = timeToShowNotification || manualSync;
   1890                 }
   1891             }
   1892 
   1893             if (shouldCancel && !shouldInstall) {
   1894                 mNeedSyncActiveNotification = false;
   1895                 sendSyncStateIntent();
   1896                 mSyncNotificationInfo.isActive = false;
   1897             }
   1898 
   1899             if (shouldInstall) {
   1900                 SyncOperation syncOperation = mActiveSyncContext.mSyncOperation;
   1901                 mNeedSyncActiveNotification = true;
   1902                 sendSyncStateIntent();
   1903                 mSyncNotificationInfo.isActive = true;
   1904                 mSyncNotificationInfo.account = syncOperation.account;
   1905                 mSyncNotificationInfo.authority = syncOperation.authority;
   1906             }
   1907         }
   1908 
   1909         /**
   1910          * Check if there were any long-lasting errors, if so install the error notification,
   1911          * otherwise cancel the error notification.
   1912          */
   1913         private void manageErrorNotification() {
   1914             //
   1915             long when = mSyncStorageEngine.getInitialSyncFailureTime();
   1916             if ((when > 0) && (when + ERROR_NOTIFICATION_DELAY_MS < System.currentTimeMillis())) {
   1917                 if (!mErrorNotificationInstalled) {
   1918                     mNeedSyncErrorNotification = true;
   1919                     sendSyncStateIntent();
   1920                 }
   1921                 mErrorNotificationInstalled = true;
   1922             } else {
   1923                 if (mErrorNotificationInstalled) {
   1924                     mNeedSyncErrorNotification = false;
   1925                     sendSyncStateIntent();
   1926                 }
   1927                 mErrorNotificationInstalled = false;
   1928             }
   1929         }
   1930 
   1931         private void manageSyncAlarm(Long earliestFuturePollElapsedTime) {
   1932             // in each of these cases the sync loop will be kicked, which will cause this
   1933             // method to be called again
   1934             if (!mDataConnectionIsConnected) return;
   1935             if (mStorageIsLow) return;
   1936 
   1937             final long now = SystemClock.elapsedRealtime();
   1938 
   1939             // Compute the alarm fire time:
   1940             // - not syncing: time of the next sync operation
   1941             // - syncing, no notification: time from sync start to notification create time
   1942             // - syncing, with notification: time till timeout of the active sync operation
   1943             Long alarmTime;
   1944             ActiveSyncContext activeSyncContext = mActiveSyncContext;
   1945             if (activeSyncContext == null) {
   1946                 synchronized (mSyncQueue) {
   1947                     final Pair<SyncOperation, Long> candidate = mSyncQueue.nextOperation();
   1948                     if (earliestFuturePollElapsedTime == null && candidate == null) {
   1949                         alarmTime = null;
   1950                     } else if (earliestFuturePollElapsedTime == null) {
   1951                         alarmTime = candidate.second;
   1952                     } else if (candidate == null) {
   1953                         alarmTime = earliestFuturePollElapsedTime;
   1954                     } else {
   1955                         alarmTime = Math.min(earliestFuturePollElapsedTime, candidate.second);
   1956                     }
   1957                 }
   1958             } else {
   1959                 final long notificationTime =
   1960                         mSyncHandler.mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY;
   1961                 final long timeoutTime =
   1962                         mActiveSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC;
   1963                 if (mSyncHandler.mSyncNotificationInfo.isActive) {
   1964                     alarmTime = timeoutTime;
   1965                 } else {
   1966                     alarmTime = Math.min(notificationTime, timeoutTime);
   1967                 }
   1968             }
   1969 
   1970             // adjust the alarmTime so that we will wake up when it is time to
   1971             // install the error notification
   1972             if (!mErrorNotificationInstalled) {
   1973                 long when = mSyncStorageEngine.getInitialSyncFailureTime();
   1974                 if (when > 0) {
   1975                     when += ERROR_NOTIFICATION_DELAY_MS;
   1976                     // convert when fron absolute time to elapsed run time
   1977                     long delay = when - System.currentTimeMillis();
   1978                     when = now + delay;
   1979                     alarmTime = alarmTime != null ? Math.min(alarmTime, when) : when;
   1980                 }
   1981             }
   1982 
   1983             // determine if we need to set or cancel the alarm
   1984             boolean shouldSet = false;
   1985             boolean shouldCancel = false;
   1986             final boolean alarmIsActive = mAlarmScheduleTime != null;
   1987             final boolean needAlarm = alarmTime != null;
   1988             if (needAlarm) {
   1989                 if (!alarmIsActive || alarmTime < mAlarmScheduleTime) {
   1990                     shouldSet = true;
   1991                 }
   1992             } else {
   1993                 shouldCancel = alarmIsActive;
   1994             }
   1995 
   1996             // set or cancel the alarm as directed
   1997             ensureAlarmService();
   1998             if (shouldSet) {
   1999                 mAlarmScheduleTime = alarmTime;
   2000                 mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime,
   2001                         mSyncAlarmIntent);
   2002             } else if (shouldCancel) {
   2003                 mAlarmScheduleTime = null;
   2004                 mAlarmService.cancel(mSyncAlarmIntent);
   2005             }
   2006         }
   2007 
   2008         private void sendSyncStateIntent() {
   2009             Intent syncStateIntent = new Intent(Intent.ACTION_SYNC_STATE_CHANGED);
   2010             syncStateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
   2011             syncStateIntent.putExtra("active", mNeedSyncActiveNotification);
   2012             syncStateIntent.putExtra("failing", mNeedSyncErrorNotification);
   2013             mContext.sendBroadcast(syncStateIntent);
   2014         }
   2015 
   2016         private void installHandleTooManyDeletesNotification(Account account, String authority,
   2017                 long numDeletes) {
   2018             if (mNotificationMgr == null) return;
   2019 
   2020             final ProviderInfo providerInfo = mContext.getPackageManager().resolveContentProvider(
   2021                     authority, 0 /* flags */);
   2022             if (providerInfo == null) {
   2023                 return;
   2024             }
   2025             CharSequence authorityName = providerInfo.loadLabel(mContext.getPackageManager());
   2026 
   2027             Intent clickIntent = new Intent();
   2028             clickIntent.setClassName("com.android.providers.subscribedfeeds",
   2029                     "com.android.settings.SyncActivityTooManyDeletes");
   2030             clickIntent.putExtra("account", account);
   2031             clickIntent.putExtra("authority", authority);
   2032             clickIntent.putExtra("provider", authorityName.toString());
   2033             clickIntent.putExtra("numDeletes", numDeletes);
   2034 
   2035             if (!isActivityAvailable(clickIntent)) {
   2036                 Log.w(TAG, "No activity found to handle too many deletes.");
   2037                 return;
   2038             }
   2039 
   2040             final PendingIntent pendingIntent = PendingIntent
   2041                     .getActivity(mContext, 0, clickIntent, PendingIntent.FLAG_CANCEL_CURRENT);
   2042 
   2043             CharSequence tooManyDeletesDescFormat = mContext.getResources().getText(
   2044                     R.string.contentServiceTooManyDeletesNotificationDesc);
   2045 
   2046             Notification notification =
   2047                 new Notification(R.drawable.stat_notify_sync_error,
   2048                         mContext.getString(R.string.contentServiceSync),
   2049                         System.currentTimeMillis());
   2050             notification.setLatestEventInfo(mContext,
   2051                     mContext.getString(R.string.contentServiceSyncNotificationTitle),
   2052                     String.format(tooManyDeletesDescFormat.toString(), authorityName),
   2053                     pendingIntent);
   2054             notification.flags |= Notification.FLAG_ONGOING_EVENT;
   2055             mNotificationMgr.notify(account.hashCode() ^ authority.hashCode(), notification);
   2056         }
   2057 
   2058         /**
   2059          * Checks whether an activity exists on the system image for the given intent.
   2060          *
   2061          * @param intent The intent for an activity.
   2062          * @return Whether or not an activity exists.
   2063          */
   2064         private boolean isActivityAvailable(Intent intent) {
   2065             PackageManager pm = mContext.getPackageManager();
   2066             List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
   2067             int listSize = list.size();
   2068             for (int i = 0; i < listSize; i++) {
   2069                 ResolveInfo resolveInfo = list.get(i);
   2070                 if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
   2071                         != 0) {
   2072                     return true;
   2073                 }
   2074             }
   2075 
   2076             return false;
   2077         }
   2078 
   2079         public long insertStartSyncEvent(SyncOperation syncOperation) {
   2080             final int source = syncOperation.syncSource;
   2081             final long now = System.currentTimeMillis();
   2082 
   2083             EventLog.writeEvent(2720, syncOperation.authority,
   2084                                 SyncStorageEngine.EVENT_START, source,
   2085                                 syncOperation.account.name.hashCode());
   2086 
   2087             return mSyncStorageEngine.insertStartSyncEvent(
   2088                     syncOperation.account, syncOperation.authority, now, source);
   2089         }
   2090 
   2091         public void stopSyncEvent(long rowId, SyncOperation syncOperation, String resultMessage,
   2092                 int upstreamActivity, int downstreamActivity, long elapsedTime) {
   2093             EventLog.writeEvent(2720, syncOperation.authority,
   2094                                 SyncStorageEngine.EVENT_STOP, syncOperation.syncSource,
   2095                                 syncOperation.account.name.hashCode());
   2096 
   2097             mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime,
   2098                     resultMessage, downstreamActivity, upstreamActivity);
   2099         }
   2100     }
   2101 }
   2102