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