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