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