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 com.android.server.content;
     18 
     19 import android.accounts.Account;
     20 import android.accounts.AccountAndUser;
     21 import android.accounts.AccountManager;
     22 import android.app.ActivityManager;
     23 import android.app.AlarmManager;
     24 import android.app.AppGlobals;
     25 import android.app.Notification;
     26 import android.app.NotificationManager;
     27 import android.app.PendingIntent;
     28 import android.content.BroadcastReceiver;
     29 import android.content.ComponentName;
     30 import android.content.ContentResolver;
     31 import android.content.Context;
     32 import android.content.ISyncAdapter;
     33 import android.content.ISyncContext;
     34 import android.content.ISyncServiceAdapter;
     35 import android.content.ISyncStatusObserver;
     36 import android.content.Intent;
     37 import android.content.IntentFilter;
     38 import android.content.PeriodicSync;
     39 import android.content.ServiceConnection;
     40 import android.content.SyncActivityTooManyDeletes;
     41 import android.content.SyncAdapterType;
     42 import android.content.SyncAdaptersCache;
     43 import android.content.SyncInfo;
     44 import android.content.SyncResult;
     45 import android.content.SyncStatusInfo;
     46 import android.content.pm.ApplicationInfo;
     47 import android.content.pm.PackageInfo;
     48 import android.content.pm.PackageManager;
     49 import android.content.pm.PackageManager.NameNotFoundException;
     50 import android.content.pm.ProviderInfo;
     51 import android.content.pm.RegisteredServicesCache;
     52 import android.content.pm.RegisteredServicesCacheListener;
     53 import android.content.pm.ResolveInfo;
     54 import android.content.pm.UserInfo;
     55 import android.net.ConnectivityManager;
     56 import android.net.NetworkInfo;
     57 import android.os.BatteryStats;
     58 import android.os.Bundle;
     59 import android.os.Handler;
     60 import android.os.IBinder;
     61 import android.os.Looper;
     62 import android.os.Message;
     63 import android.os.PowerManager;
     64 import android.os.RemoteException;
     65 import android.os.ServiceManager;
     66 import android.os.SystemClock;
     67 import android.os.SystemProperties;
     68 import android.os.UserHandle;
     69 import android.os.UserManager;
     70 import android.os.WorkSource;
     71 import android.provider.Settings;
     72 import android.text.format.DateUtils;
     73 import android.text.format.Time;
     74 import android.text.TextUtils;
     75 import android.util.EventLog;
     76 import android.util.Log;
     77 import android.util.Pair;
     78 
     79 import com.android.internal.R;
     80 import com.android.internal.annotations.GuardedBy;
     81 import com.android.internal.app.IBatteryStats;
     82 import com.android.internal.os.BackgroundThread;
     83 import com.android.internal.util.IndentingPrintWriter;
     84 import com.android.server.accounts.AccountManagerService;
     85 import com.android.server.content.SyncStorageEngine.AuthorityInfo;
     86 import com.android.server.content.SyncStorageEngine.OnSyncRequestListener;
     87 import com.google.android.collect.Lists;
     88 import com.google.android.collect.Maps;
     89 import com.google.android.collect.Sets;
     90 
     91 import java.io.FileDescriptor;
     92 import java.io.PrintWriter;
     93 import java.util.ArrayList;
     94 import java.util.Arrays;
     95 import java.util.Collection;
     96 import java.util.Collections;
     97 import java.util.Comparator;
     98 import java.util.HashMap;
     99 import java.util.HashSet;
    100 import java.util.Iterator;
    101 import java.util.List;
    102 import java.util.Map;
    103 import java.util.Random;
    104 import java.util.Set;
    105 
    106 /**
    107  * @hide
    108  */
    109 public class SyncManager {
    110     private static final String TAG = "SyncManager";
    111 
    112     /** Delay a sync due to local changes this long. In milliseconds */
    113     private static final long LOCAL_SYNC_DELAY;
    114 
    115     /**
    116      * If a sync takes longer than this and the sync queue is not empty then we will
    117      * cancel it and add it back to the end of the sync queue. In milliseconds.
    118      */
    119     private static final long MAX_TIME_PER_SYNC;
    120 
    121     static {
    122         final boolean isLargeRAM = !ActivityManager.isLowRamDeviceStatic();
    123         int defaultMaxInitSyncs = isLargeRAM ? 5 : 2;
    124         int defaultMaxRegularSyncs = isLargeRAM ? 2 : 1;
    125         MAX_SIMULTANEOUS_INITIALIZATION_SYNCS =
    126                 SystemProperties.getInt("sync.max_init_syncs", defaultMaxInitSyncs);
    127         MAX_SIMULTANEOUS_REGULAR_SYNCS =
    128                 SystemProperties.getInt("sync.max_regular_syncs", defaultMaxRegularSyncs);
    129         LOCAL_SYNC_DELAY =
    130                 SystemProperties.getLong("sync.local_sync_delay", 30 * 1000 /* 30 seconds */);
    131         MAX_TIME_PER_SYNC =
    132                 SystemProperties.getLong("sync.max_time_per_sync", 5 * 60 * 1000 /* 5 minutes */);
    133         SYNC_NOTIFICATION_DELAY =
    134                 SystemProperties.getLong("sync.notification_delay", 30 * 1000 /* 30 seconds */);
    135     }
    136 
    137     private static final long SYNC_NOTIFICATION_DELAY;
    138 
    139     /**
    140      * When retrying a sync for the first time use this delay. After that
    141      * the retry time will double until it reached MAX_SYNC_RETRY_TIME.
    142      * In milliseconds.
    143      */
    144     private static final long INITIAL_SYNC_RETRY_TIME_IN_MS = 30 * 1000; // 30 seconds
    145 
    146     /**
    147      * Default the max sync retry time to this value.
    148      */
    149     private static final long DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS = 60 * 60; // one hour
    150 
    151     /**
    152      * How long to wait before retrying a sync that failed due to one already being in progress.
    153      */
    154     private static final int DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS = 10;
    155 
    156     /**
    157      * How long to wait before considering an active sync to have timed-out, and cancelling it.
    158      */
    159     private static final long ACTIVE_SYNC_TIMEOUT_MILLIS = 30L * 60 * 1000;  // 30 mins.
    160 
    161     private static final String SYNC_WAKE_LOCK_PREFIX = "*sync*/";
    162     private static final String HANDLE_SYNC_ALARM_WAKE_LOCK = "SyncManagerHandleSyncAlarm";
    163     private static final String SYNC_LOOP_WAKE_LOCK = "SyncLoopWakeLock";
    164 
    165     private static final int MAX_SIMULTANEOUS_REGULAR_SYNCS;
    166     private static final int MAX_SIMULTANEOUS_INITIALIZATION_SYNCS;
    167 
    168     private Context mContext;
    169 
    170     private static final AccountAndUser[] INITIAL_ACCOUNTS_ARRAY = new AccountAndUser[0];
    171 
    172     // TODO: add better locking around mRunningAccounts
    173     private volatile AccountAndUser[] mRunningAccounts = INITIAL_ACCOUNTS_ARRAY;
    174 
    175     volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
    176     volatile private PowerManager.WakeLock mSyncManagerWakeLock;
    177     volatile private boolean mDataConnectionIsConnected = false;
    178     volatile private boolean mStorageIsLow = false;
    179 
    180     private final NotificationManager mNotificationMgr;
    181     private AlarmManager mAlarmService = null;
    182     private final IBatteryStats mBatteryStats;
    183 
    184     private SyncStorageEngine mSyncStorageEngine;
    185 
    186     @GuardedBy("mSyncQueue")
    187     private final SyncQueue mSyncQueue;
    188 
    189     protected final ArrayList<ActiveSyncContext> mActiveSyncContexts = Lists.newArrayList();
    190 
    191     // set if the sync active indicator should be reported
    192     private boolean mNeedSyncActiveNotification = false;
    193 
    194     private final PendingIntent mSyncAlarmIntent;
    195     // Synchronized on "this". Instead of using this directly one should instead call
    196     // its accessor, getConnManager().
    197     private ConnectivityManager mConnManagerDoNotUseDirectly;
    198 
    199     protected SyncAdaptersCache mSyncAdapters;
    200 
    201     private BroadcastReceiver mStorageIntentReceiver =
    202             new BroadcastReceiver() {
    203                 @Override
    204                 public void onReceive(Context context, Intent intent) {
    205                     String action = intent.getAction();
    206                     if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) {
    207                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    208                             Log.v(TAG, "Internal storage is low.");
    209                         }
    210                         mStorageIsLow = true;
    211                         cancelActiveSync(
    212                                 SyncStorageEngine.EndPoint.USER_ALL_PROVIDER_ALL_ACCOUNTS_ALL,
    213                                 null /* any sync */);
    214                     } else if (Intent.ACTION_DEVICE_STORAGE_OK.equals(action)) {
    215                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    216                             Log.v(TAG, "Internal storage is ok.");
    217                         }
    218                         mStorageIsLow = false;
    219                         sendCheckAlarmsMessage();
    220                     }
    221                 }
    222             };
    223 
    224     private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
    225         @Override
    226         public void onReceive(Context context, Intent intent) {
    227             mSyncHandler.onBootCompleted();
    228         }
    229     };
    230 
    231     private BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() {
    232         @Override
    233         public void onReceive(Context context, Intent intent) {
    234             updateRunningAccounts();
    235 
    236             // Kick off sync for everyone, since this was a radical account change
    237             scheduleSync(null, UserHandle.USER_ALL, SyncOperation.REASON_ACCOUNTS_UPDATED, null,
    238                     null, 0 /* no delay */, 0/* no delay */, false);
    239         }
    240     };
    241 
    242     private final PowerManager mPowerManager;
    243 
    244     // Use this as a random offset to seed all periodic syncs.
    245     private int mSyncRandomOffsetMillis;
    246 
    247     private final UserManager mUserManager;
    248 
    249     private static final long SYNC_ALARM_TIMEOUT_MIN = 30 * 1000; // 30 seconds
    250     private static final long SYNC_ALARM_TIMEOUT_MAX = 2 * 60 * 60 * 1000; // two hours
    251 
    252     private List<UserInfo> getAllUsers() {
    253         return mUserManager.getUsers();
    254     }
    255 
    256     private boolean containsAccountAndUser(AccountAndUser[] accounts, Account account, int userId) {
    257         boolean found = false;
    258         for (int i = 0; i < accounts.length; i++) {
    259             if (accounts[i].userId == userId
    260                     && accounts[i].account.equals(account)) {
    261                 found = true;
    262                 break;
    263             }
    264         }
    265         return found;
    266     }
    267 
    268     public void updateRunningAccounts() {
    269         mRunningAccounts = AccountManagerService.getSingleton().getRunningAccounts();
    270 
    271         if (mBootCompleted) {
    272             doDatabaseCleanup();
    273         }
    274 
    275         AccountAndUser[] accounts = mRunningAccounts;
    276         for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
    277             if (!containsAccountAndUser(accounts,
    278                     currentSyncContext.mSyncOperation.target.account,
    279                     currentSyncContext.mSyncOperation.target.userId)) {
    280                 Log.d(TAG, "canceling sync since the account is no longer running");
    281                 sendSyncFinishedOrCanceledMessage(currentSyncContext,
    282                         null /* no result since this is a cancel */);
    283             }
    284         }
    285         // we must do this since we don't bother scheduling alarms when
    286         // the accounts are not set yet
    287         sendCheckAlarmsMessage();
    288     }
    289 
    290     private void doDatabaseCleanup() {
    291         for (UserInfo user : mUserManager.getUsers(true)) {
    292             // Skip any partially created/removed users
    293             if (user.partial) continue;
    294             Account[] accountsForUser = AccountManagerService.getSingleton().getAccounts(user.id);
    295             mSyncStorageEngine.doDatabaseCleanup(accountsForUser, user.id);
    296         }
    297     }
    298 
    299     private BroadcastReceiver mConnectivityIntentReceiver =
    300             new BroadcastReceiver() {
    301         @Override
    302         public void onReceive(Context context, Intent intent) {
    303             final boolean wasConnected = mDataConnectionIsConnected;
    304 
    305             // don't use the intent to figure out if network is connected, just check
    306             // ConnectivityManager directly.
    307             mDataConnectionIsConnected = readDataConnectionState();
    308             if (mDataConnectionIsConnected) {
    309                 if (!wasConnected) {
    310                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
    311                         Log.v(TAG, "Reconnection detected: clearing all backoffs");
    312                     }
    313                     synchronized (mSyncQueue) {
    314                         mSyncStorageEngine.clearAllBackoffsLocked(mSyncQueue);
    315                     }
    316                 }
    317                 sendCheckAlarmsMessage();
    318             }
    319         }
    320     };
    321 
    322     private boolean readDataConnectionState() {
    323         NetworkInfo networkInfo = getConnectivityManager().getActiveNetworkInfo();
    324         return (networkInfo != null) && networkInfo.isConnected();
    325     }
    326 
    327     private BroadcastReceiver mShutdownIntentReceiver =
    328             new BroadcastReceiver() {
    329         @Override
    330         public void onReceive(Context context, Intent intent) {
    331             Log.w(TAG, "Writing sync state before shutdown...");
    332             getSyncStorageEngine().writeAllState();
    333         }
    334     };
    335 
    336     private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() {
    337         @Override
    338         public void onReceive(Context context, Intent intent) {
    339             String action = intent.getAction();
    340             final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
    341             if (userId == UserHandle.USER_NULL) return;
    342 
    343             if (Intent.ACTION_USER_REMOVED.equals(action)) {
    344                 onUserRemoved(userId);
    345             } else if (Intent.ACTION_USER_STARTING.equals(action)) {
    346                 onUserStarting(userId);
    347             } else if (Intent.ACTION_USER_STOPPING.equals(action)) {
    348                 onUserStopping(userId);
    349             }
    350         }
    351     };
    352 
    353     private static final String ACTION_SYNC_ALARM = "android.content.syncmanager.SYNC_ALARM";
    354     private final SyncHandler mSyncHandler;
    355 
    356     private volatile boolean mBootCompleted = false;
    357 
    358     private ConnectivityManager getConnectivityManager() {
    359         synchronized (this) {
    360             if (mConnManagerDoNotUseDirectly == null) {
    361                 mConnManagerDoNotUseDirectly = (ConnectivityManager)mContext.getSystemService(
    362                         Context.CONNECTIVITY_SERVICE);
    363             }
    364             return mConnManagerDoNotUseDirectly;
    365         }
    366     }
    367 
    368     /**
    369      * Should only be created after {@link ContentService#systemReady()} so that
    370      * {@link PackageManager} is ready to query.
    371      */
    372     public SyncManager(Context context, boolean factoryTest) {
    373         // Initialize the SyncStorageEngine first, before registering observers
    374         // and creating threads and so on; it may fail if the disk is full.
    375         mContext = context;
    376 
    377         SyncStorageEngine.init(context);
    378         mSyncStorageEngine = SyncStorageEngine.getSingleton();
    379         mSyncStorageEngine.setOnSyncRequestListener(new OnSyncRequestListener() {
    380             @Override
    381             public void onSyncRequest(SyncStorageEngine.EndPoint info, int reason, Bundle extras) {
    382                 if (info.target_provider) {
    383                     scheduleSync(info.account, info.userId, reason, info.provider, extras,
    384                         0 /* no flex */,
    385                         0 /* run immediately */,
    386                         false);
    387                 } else if (info.target_service) {
    388                     scheduleSync(info.service, info.userId, reason, extras,
    389                             0 /* no flex */,
    390                             0 /* run immediately */);
    391                 }
    392             }
    393         });
    394 
    395         mSyncAdapters = new SyncAdaptersCache(mContext);
    396         mSyncQueue = new SyncQueue(mContext.getPackageManager(), mSyncStorageEngine, mSyncAdapters);
    397 
    398         mSyncHandler = new SyncHandler(BackgroundThread.get().getLooper());
    399 
    400         mSyncAdapters.setListener(new RegisteredServicesCacheListener<SyncAdapterType>() {
    401             @Override
    402             public void onServiceChanged(SyncAdapterType type, int userId, boolean removed) {
    403                 if (!removed) {
    404                     scheduleSync(null, UserHandle.USER_ALL,
    405                             SyncOperation.REASON_SERVICE_CHANGED,
    406                             type.authority, null, 0 /* no delay */, 0 /* no delay */,
    407                             false /* onlyThoseWithUnkownSyncableState */);
    408                 }
    409             }
    410         }, mSyncHandler);
    411 
    412         mSyncAlarmIntent = PendingIntent.getBroadcast(
    413                 mContext, 0 /* ignored */, new Intent(ACTION_SYNC_ALARM), 0);
    414 
    415         IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
    416         context.registerReceiver(mConnectivityIntentReceiver, intentFilter);
    417 
    418         if (!factoryTest) {
    419             intentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
    420             intentFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
    421             context.registerReceiver(mBootCompletedReceiver, intentFilter);
    422         }
    423 
    424         intentFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
    425         intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
    426         context.registerReceiver(mStorageIntentReceiver, intentFilter);
    427 
    428         intentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
    429         intentFilter.setPriority(100);
    430         context.registerReceiver(mShutdownIntentReceiver, intentFilter);
    431 
    432         intentFilter = new IntentFilter();
    433         intentFilter.addAction(Intent.ACTION_USER_REMOVED);
    434         intentFilter.addAction(Intent.ACTION_USER_STARTING);
    435         intentFilter.addAction(Intent.ACTION_USER_STOPPING);
    436         mContext.registerReceiverAsUser(
    437                 mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null);
    438 
    439         if (!factoryTest) {
    440             mNotificationMgr = (NotificationManager)
    441                 context.getSystemService(Context.NOTIFICATION_SERVICE);
    442             context.registerReceiver(new SyncAlarmIntentReceiver(),
    443                     new IntentFilter(ACTION_SYNC_ALARM));
    444         } else {
    445             mNotificationMgr = null;
    446         }
    447         mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
    448         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
    449         mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService(
    450                 BatteryStats.SERVICE_NAME));
    451 
    452         // This WakeLock is used to ensure that we stay awake between the time that we receive
    453         // a sync alarm notification and when we finish processing it. We need to do this
    454         // because we don't do the work in the alarm handler, rather we do it in a message
    455         // handler.
    456         mHandleAlarmWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
    457                 HANDLE_SYNC_ALARM_WAKE_LOCK);
    458         mHandleAlarmWakeLock.setReferenceCounted(false);
    459 
    460         // This WakeLock is used to ensure that we stay awake while running the sync loop
    461         // message handler. Normally we will hold a sync adapter wake lock while it is being
    462         // synced but during the execution of the sync loop it might finish a sync for
    463         // one sync adapter before starting the sync for the other sync adapter and we
    464         // don't want the device to go to sleep during that window.
    465         mSyncManagerWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
    466                 SYNC_LOOP_WAKE_LOCK);
    467         mSyncManagerWakeLock.setReferenceCounted(false);
    468 
    469         mSyncStorageEngine.addStatusChangeListener(
    470                 ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, new ISyncStatusObserver.Stub() {
    471             @Override
    472             public void onStatusChanged(int which) {
    473                 // force the sync loop to run if the settings change
    474                 sendCheckAlarmsMessage();
    475             }
    476         });
    477 
    478         if (!factoryTest) {
    479             // Register for account list updates for all users
    480             mContext.registerReceiverAsUser(mAccountsUpdatedReceiver,
    481                     UserHandle.ALL,
    482                     new IntentFilter(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION),
    483                     null, null);
    484         }
    485 
    486         // Pick a random second in a day to seed all periodic syncs
    487         mSyncRandomOffsetMillis = mSyncStorageEngine.getSyncRandomOffset() * 1000;
    488     }
    489 
    490     /**
    491      * Return a random value v that satisfies minValue <= v < maxValue. The difference between
    492      * maxValue and minValue must be less than Integer.MAX_VALUE.
    493      */
    494     private long jitterize(long minValue, long maxValue) {
    495         Random random = new Random(SystemClock.elapsedRealtime());
    496         long spread = maxValue - minValue;
    497         if (spread > Integer.MAX_VALUE) {
    498             throw new IllegalArgumentException("the difference between the maxValue and the "
    499                     + "minValue must be less than " + Integer.MAX_VALUE);
    500         }
    501         return minValue + random.nextInt((int)spread);
    502     }
    503 
    504     public SyncStorageEngine getSyncStorageEngine() {
    505         return mSyncStorageEngine;
    506     }
    507 
    508     public int getIsSyncable(Account account, int userId, String providerName) {
    509         int isSyncable = mSyncStorageEngine.getIsSyncable(account, userId, providerName);
    510         UserInfo userInfo = UserManager.get(mContext).getUserInfo(userId);
    511 
    512         // If it's not a restricted user, return isSyncable
    513         if (userInfo == null || !userInfo.isRestricted()) return isSyncable;
    514 
    515         // Else check if the sync adapter has opted-in or not
    516         RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
    517                 mSyncAdapters.getServiceInfo(
    518                 SyncAdapterType.newKey(providerName, account.type), userId);
    519         if (syncAdapterInfo == null) return isSyncable;
    520 
    521         PackageInfo pInfo = null;
    522         try {
    523             pInfo = AppGlobals.getPackageManager().getPackageInfo(
    524                 syncAdapterInfo.componentName.getPackageName(), 0, userId);
    525             if (pInfo == null) return isSyncable;
    526         } catch (RemoteException re) {
    527             // Shouldn't happen
    528             return isSyncable;
    529         }
    530         if (pInfo.restrictedAccountType != null
    531                 && pInfo.restrictedAccountType.equals(account.type)) {
    532             return isSyncable;
    533         } else {
    534             return 0;
    535         }
    536     }
    537 
    538     private void ensureAlarmService() {
    539         if (mAlarmService == null) {
    540             mAlarmService = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
    541         }
    542     }
    543 
    544     /**
    545      * Initiate a sync using the new anonymous service API.
    546      * @param cname SyncService component bound to in order to perform the sync.
    547      * @param userId the id of the user whose accounts are to be synced. If userId is USER_ALL,
    548      *          then all users' accounts are considered.
    549      * @param uid Linux uid of the application that is performing the sync.
    550      * @param extras a Map of SyncAdapter-specific information to control
    551      *          syncs of a specific provider. Cannot be null.
    552      * @param beforeRunTimeMillis milliseconds before <code>runtimeMillis</code> that this sync may
    553      * be run.
    554      * @param runtimeMillis milliseconds from now by which this sync must be run.
    555      */
    556     public void scheduleSync(ComponentName cname, int userId, int uid, Bundle extras,
    557             long beforeRunTimeMillis, long runtimeMillis) {
    558         boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
    559         if (isLoggable) {
    560             Log.d(TAG, "one off sync for: " + cname + " " + extras.toString());
    561         }
    562 
    563         Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
    564         if (expedited) {
    565             runtimeMillis = -1; // this means schedule at the front of the queue
    566         }
    567 
    568         final boolean ignoreSettings =
    569                 extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
    570         int source = SyncStorageEngine.SOURCE_SERVICE;
    571         boolean isEnabled = mSyncStorageEngine.getIsTargetServiceActive(cname, userId);
    572         // Only schedule this sync if
    573         //   - we've explicitly been told to ignore settings.
    574         //   - global sync is enabled for this user.
    575         boolean syncAllowed =
    576                 ignoreSettings
    577                 || mSyncStorageEngine.getMasterSyncAutomatically(userId);
    578         if (!syncAllowed) {
    579             if (isLoggable) {
    580                 Log.d(TAG, "scheduleSync: sync of " + cname + " not allowed, dropping request.");
    581             }
    582             return;
    583         }
    584         if (!isEnabled) {
    585             if (isLoggable) {
    586                 Log.d(TAG, "scheduleSync: " + cname + " is not enabled, dropping request");
    587             }
    588             return;
    589         }
    590         SyncStorageEngine.EndPoint info = new SyncStorageEngine.EndPoint(cname, userId);
    591         Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(info);
    592         long delayUntil = mSyncStorageEngine.getDelayUntilTime(info);
    593         final long backoffTime = backoff != null ? backoff.first : 0;
    594         if (isLoggable) {
    595                 Log.v(TAG, "schedule Sync:"
    596                         + ", delay until " + delayUntil
    597                         + ", run by " + runtimeMillis
    598                         + ", flex " + beforeRunTimeMillis
    599                         + ", source " + source
    600                         + ", sync service " + cname
    601                         + ", extras " + extras);
    602         }
    603         scheduleSyncOperation(
    604                 new SyncOperation(cname, userId, uid, source, extras,
    605                         runtimeMillis /* runtime */,
    606                         beforeRunTimeMillis /* flextime */,
    607                         backoffTime,
    608                         delayUntil));
    609     }
    610 
    611     /**
    612      * Initiate a sync. This can start a sync for all providers
    613      * (pass null to url, set onlyTicklable to false), only those
    614      * providers that are marked as ticklable (pass null to url,
    615      * set onlyTicklable to true), or a specific provider (set url
    616      * to the content url of the provider).
    617      *
    618      * <p>If the ContentResolver.SYNC_EXTRAS_UPLOAD boolean in extras is
    619      * true then initiate a sync that just checks for local changes to send
    620      * to the server, otherwise initiate a sync that first gets any
    621      * changes from the server before sending local changes back to
    622      * the server.
    623      *
    624      * <p>If a specific provider is being synced (the url is non-null)
    625      * then the extras can contain SyncAdapter-specific information
    626      * to control what gets synced (e.g. which specific feed to sync).
    627      *
    628      * <p>You'll start getting callbacks after this.
    629      *
    630      * @param requestedAccount the account to sync, may be null to signify all accounts
    631      * @param userId the id of the user whose accounts are to be synced. If userId is USER_ALL,
    632      *          then all users' accounts are considered.
    633      * @param reason for sync request. If this is a positive integer, it is the Linux uid
    634      * assigned to the process that requested the sync. If it's negative, the sync was requested by
    635      * the SyncManager itself and could be one of the following:
    636      *      {@link SyncOperation#REASON_BACKGROUND_DATA_SETTINGS_CHANGED}
    637      *      {@link SyncOperation#REASON_ACCOUNTS_UPDATED}
    638      *      {@link SyncOperation#REASON_SERVICE_CHANGED}
    639      *      {@link SyncOperation#REASON_PERIODIC}
    640      *      {@link SyncOperation#REASON_IS_SYNCABLE}
    641      *      {@link SyncOperation#REASON_SYNC_AUTO}
    642      *      {@link SyncOperation#REASON_MASTER_SYNC_AUTO}
    643      *      {@link SyncOperation#REASON_USER_START}
    644      * @param requestedAuthority the authority to sync, may be null to indicate all authorities
    645      * @param extras a Map of SyncAdapter-specific information to control
    646      *          syncs of a specific provider. Can be null. Is ignored
    647      *          if the url is null.
    648      * @param beforeRuntimeMillis milliseconds before runtimeMillis that this sync can run.
    649      * @param runtimeMillis maximum milliseconds in the future to wait before performing sync.
    650      * @param onlyThoseWithUnkownSyncableState Only sync authorities that have unknown state.
    651      */
    652     public void scheduleSync(Account requestedAccount, int userId, int reason,
    653             String requestedAuthority, Bundle extras, long beforeRuntimeMillis,
    654             long runtimeMillis, boolean onlyThoseWithUnkownSyncableState) {
    655         boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
    656 
    657         if (extras == null) {
    658             extras = new Bundle();
    659         }
    660         if (isLoggable) {
    661             Log.d(TAG, "one-time sync for: " + requestedAccount + " " + extras.toString() + " "
    662                     + requestedAuthority);
    663         }
    664         Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
    665         if (expedited) {
    666             runtimeMillis = -1; // this means schedule at the front of the queue
    667         }
    668 
    669         AccountAndUser[] accounts;
    670         if (requestedAccount != null && userId != UserHandle.USER_ALL) {
    671             accounts = new AccountAndUser[] { new AccountAndUser(requestedAccount, userId) };
    672         } else {
    673             accounts = mRunningAccounts;
    674             if (accounts.length == 0) {
    675                 if (isLoggable) {
    676                     Log.v(TAG, "scheduleSync: no accounts configured, dropping");
    677                 }
    678                 return;
    679             }
    680         }
    681 
    682         final boolean uploadOnly = extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false);
    683         final boolean manualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
    684         if (manualSync) {
    685             extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, true);
    686             extras.putBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, true);
    687         }
    688         final boolean ignoreSettings =
    689                 extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false);
    690 
    691         int source;
    692         if (uploadOnly) {
    693             source = SyncStorageEngine.SOURCE_LOCAL;
    694         } else if (manualSync) {
    695             source = SyncStorageEngine.SOURCE_USER;
    696         } else if (requestedAuthority == null) {
    697             source = SyncStorageEngine.SOURCE_POLL;
    698         } else {
    699             // this isn't strictly server, since arbitrary callers can (and do) request
    700             // a non-forced two-way sync on a specific url
    701             source = SyncStorageEngine.SOURCE_SERVER;
    702         }
    703 
    704         for (AccountAndUser account : accounts) {
    705             // Compile a list of authorities that have sync adapters.
    706             // For each authority sync each account that matches a sync adapter.
    707             final HashSet<String> syncableAuthorities = new HashSet<String>();
    708             for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter :
    709                     mSyncAdapters.getAllServices(account.userId)) {
    710                 syncableAuthorities.add(syncAdapter.type.authority);
    711             }
    712 
    713             // if the url was specified then replace the list of authorities
    714             // with just this authority or clear it if this authority isn't
    715             // syncable
    716             if (requestedAuthority != null) {
    717                 final boolean hasSyncAdapter = syncableAuthorities.contains(requestedAuthority);
    718                 syncableAuthorities.clear();
    719                 if (hasSyncAdapter) syncableAuthorities.add(requestedAuthority);
    720             }
    721 
    722             for (String authority : syncableAuthorities) {
    723                 int isSyncable = getIsSyncable(account.account, account.userId,
    724                         authority);
    725                 if (isSyncable == 0) {
    726                     continue;
    727                 }
    728                 final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
    729                 syncAdapterInfo = mSyncAdapters.getServiceInfo(
    730                         SyncAdapterType.newKey(authority, account.account.type), account.userId);
    731                 if (syncAdapterInfo == null) {
    732                     continue;
    733                 }
    734                 final boolean allowParallelSyncs = syncAdapterInfo.type.allowParallelSyncs();
    735                 final boolean isAlwaysSyncable = syncAdapterInfo.type.isAlwaysSyncable();
    736                 if (isSyncable < 0 && isAlwaysSyncable) {
    737                     mSyncStorageEngine.setIsSyncable(account.account, account.userId, authority, 1);
    738                     isSyncable = 1;
    739                 }
    740                 if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) {
    741                     continue;
    742                 }
    743                 if (!syncAdapterInfo.type.supportsUploading() && uploadOnly) {
    744                     continue;
    745                 }
    746 
    747                 boolean syncAllowed =
    748                         (isSyncable < 0) // always allow if the isSyncable state is unknown
    749                         || ignoreSettings
    750                         || (mSyncStorageEngine.getMasterSyncAutomatically(account.userId)
    751                                 && mSyncStorageEngine.getSyncAutomatically(account.account,
    752                                         account.userId, authority));
    753                 if (!syncAllowed) {
    754                     if (isLoggable) {
    755                         Log.d(TAG, "scheduleSync: sync of " + account + ", " + authority
    756                                 + " is not allowed, dropping request");
    757                     }
    758                     continue;
    759                 }
    760                 SyncStorageEngine.EndPoint info =
    761                         new SyncStorageEngine.EndPoint(
    762                                 account.account, authority, account.userId);
    763                 Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff(info);
    764                 long delayUntil =
    765                         mSyncStorageEngine.getDelayUntilTime(info);
    766                 final long backoffTime = backoff != null ? backoff.first : 0;
    767                 if (isSyncable < 0) {
    768                     // Initialisation sync.
    769                     Bundle newExtras = new Bundle();
    770                     newExtras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
    771                     if (isLoggable) {
    772                         Log.v(TAG, "schedule initialisation Sync:"
    773                                 + ", delay until " + delayUntil
    774                                 + ", run by " + 0
    775                                 + ", flex " + 0
    776                                 + ", source " + source
    777                                 + ", account " + account
    778                                 + ", authority " + authority
    779                                 + ", extras " + newExtras);
    780                     }
    781                     scheduleSyncOperation(
    782                             new SyncOperation(account.account, account.userId, reason, source,
    783                                     authority, newExtras, 0 /* immediate */, 0 /* No flex time*/,
    784                                     backoffTime, delayUntil, allowParallelSyncs));
    785                 }
    786                 if (!onlyThoseWithUnkownSyncableState) {
    787                     if (isLoggable) {
    788                         Log.v(TAG, "scheduleSync:"
    789                                 + " delay until " + delayUntil
    790                                 + " run by " + runtimeMillis
    791                                 + " flex " + beforeRuntimeMillis
    792                                 + ", source " + source
    793                                 + ", account " + account
    794                                 + ", authority " + authority
    795                                 + ", extras " + extras);
    796                     }
    797                     scheduleSyncOperation(
    798                             new SyncOperation(account.account, account.userId, reason, source,
    799                                     authority, extras, runtimeMillis, beforeRuntimeMillis,
    800                                     backoffTime, delayUntil, allowParallelSyncs));
    801                 }
    802             }
    803         }
    804     }
    805 
    806     /**
    807      * Schedule sync based on local changes to a provider. Occurs within interval
    808      * [LOCAL_SYNC_DELAY, 2*LOCAL_SYNC_DELAY].
    809      */
    810     public void scheduleLocalSync(Account account, int userId, int reason, String authority) {
    811         final Bundle extras = new Bundle();
    812         extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
    813         scheduleSync(account, userId, reason, authority, extras,
    814                 LOCAL_SYNC_DELAY /* earliest run time */,
    815                 2 * LOCAL_SYNC_DELAY /* latest sync time. */,
    816                 false /* onlyThoseWithUnkownSyncableState */);
    817     }
    818 
    819     public SyncAdapterType[] getSyncAdapterTypes(int userId) {
    820         final Collection<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> serviceInfos;
    821         serviceInfos = mSyncAdapters.getAllServices(userId);
    822         SyncAdapterType[] types = new SyncAdapterType[serviceInfos.size()];
    823         int i = 0;
    824         for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> serviceInfo : serviceInfos) {
    825             types[i] = serviceInfo.type;
    826             ++i;
    827         }
    828         return types;
    829     }
    830 
    831     private void sendSyncAlarmMessage() {
    832         if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_ALARM");
    833         mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_SYNC_ALARM);
    834     }
    835 
    836     private void sendCheckAlarmsMessage() {
    837         if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CHECK_ALARMS");
    838         mSyncHandler.removeMessages(SyncHandler.MESSAGE_CHECK_ALARMS);
    839         mSyncHandler.sendEmptyMessage(SyncHandler.MESSAGE_CHECK_ALARMS);
    840     }
    841 
    842     private void sendSyncFinishedOrCanceledMessage(ActiveSyncContext syncContext,
    843             SyncResult syncResult) {
    844         if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_SYNC_FINISHED");
    845         Message msg = mSyncHandler.obtainMessage();
    846         msg.what = SyncHandler.MESSAGE_SYNC_FINISHED;
    847         msg.obj = new SyncHandlerMessagePayload(syncContext, syncResult);
    848         mSyncHandler.sendMessage(msg);
    849     }
    850 
    851     private void sendCancelSyncsMessage(final SyncStorageEngine.EndPoint info, Bundle extras) {
    852         if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "sending MESSAGE_CANCEL");
    853         Message msg = mSyncHandler.obtainMessage();
    854         msg.what = SyncHandler.MESSAGE_CANCEL;
    855         msg.setData(extras);
    856         msg.obj = info;
    857         mSyncHandler.sendMessage(msg);
    858     }
    859 
    860     /**
    861      * Post a delayed message to the handler that will result in the cancellation of the provided
    862      * running sync's context.
    863      */
    864     private void postSyncExpiryMessage(ActiveSyncContext activeSyncContext) {
    865         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    866             Log.v(TAG, "posting MESSAGE_SYNC_EXPIRED in " +
    867                     (ACTIVE_SYNC_TIMEOUT_MILLIS/1000) + "s");
    868         }
    869         Message msg = mSyncHandler.obtainMessage();
    870         msg.what = SyncHandler.MESSAGE_SYNC_EXPIRED;
    871         msg.obj = activeSyncContext;
    872         mSyncHandler.sendMessageDelayed(msg, ACTIVE_SYNC_TIMEOUT_MILLIS);
    873     }
    874 
    875     /**
    876      * Remove any time-outs previously posted for the provided active sync.
    877      */
    878     private void removeSyncExpiryMessage(ActiveSyncContext activeSyncContext) {
    879         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    880             Log.v(TAG, "removing all MESSAGE_SYNC_EXPIRED for " + activeSyncContext.toString());
    881         }
    882         mSyncHandler.removeMessages(SyncHandler.MESSAGE_SYNC_EXPIRED, activeSyncContext);
    883     }
    884 
    885     class SyncHandlerMessagePayload {
    886         public final ActiveSyncContext activeSyncContext;
    887         public final SyncResult syncResult;
    888 
    889         SyncHandlerMessagePayload(ActiveSyncContext syncContext, SyncResult syncResult) {
    890             this.activeSyncContext = syncContext;
    891             this.syncResult = syncResult;
    892         }
    893     }
    894 
    895     class SyncAlarmIntentReceiver extends BroadcastReceiver {
    896         @Override
    897         public void onReceive(Context context, Intent intent) {
    898             mHandleAlarmWakeLock.acquire();
    899             sendSyncAlarmMessage();
    900         }
    901     }
    902 
    903     private void clearBackoffSetting(SyncOperation op) {
    904         mSyncStorageEngine.setBackoff(op.target,
    905                 SyncStorageEngine.NOT_IN_BACKOFF_MODE,
    906                 SyncStorageEngine.NOT_IN_BACKOFF_MODE);
    907         synchronized (mSyncQueue) {
    908             mSyncQueue.onBackoffChanged(op.target, 0);
    909         }
    910     }
    911 
    912     private void increaseBackoffSetting(SyncOperation op) {
    913         // TODO: Use this function to align it to an already scheduled sync
    914         //       operation in the specified window
    915         final long now = SystemClock.elapsedRealtime();
    916 
    917         final Pair<Long, Long> previousSettings =
    918                 mSyncStorageEngine.getBackoff(op.target);
    919         long newDelayInMs = -1;
    920         if (previousSettings != null) {
    921             // don't increase backoff before current backoff is expired. This will happen for op's
    922             // with ignoreBackoff set.
    923             if (now < previousSettings.first) {
    924                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
    925                     Log.v(TAG, "Still in backoff, do not increase it. "
    926                         + "Remaining: " + ((previousSettings.first - now) / 1000) + " seconds.");
    927                 }
    928                 return;
    929             }
    930             // Subsequent delays are the double of the previous delay
    931             newDelayInMs = previousSettings.second * 2;
    932         }
    933         if (newDelayInMs <= 0) {
    934             // The initial delay is the jitterized INITIAL_SYNC_RETRY_TIME_IN_MS
    935             newDelayInMs = jitterize(INITIAL_SYNC_RETRY_TIME_IN_MS,
    936                     (long)(INITIAL_SYNC_RETRY_TIME_IN_MS * 1.1));
    937         }
    938 
    939         // Cap the delay
    940         long maxSyncRetryTimeInSeconds = Settings.Global.getLong(mContext.getContentResolver(),
    941                 Settings.Global.SYNC_MAX_RETRY_DELAY_IN_SECONDS,
    942                 DEFAULT_MAX_SYNC_RETRY_TIME_IN_SECONDS);
    943         if (newDelayInMs > maxSyncRetryTimeInSeconds * 1000) {
    944             newDelayInMs = maxSyncRetryTimeInSeconds * 1000;
    945         }
    946 
    947         final long backoff = now + newDelayInMs;
    948 
    949         mSyncStorageEngine.setBackoff(op.target, backoff, newDelayInMs);
    950         op.backoff = backoff;
    951         op.updateEffectiveRunTime();
    952 
    953         synchronized (mSyncQueue) {
    954             mSyncQueue.onBackoffChanged(op.target, backoff);
    955         }
    956     }
    957 
    958     private void setDelayUntilTime(SyncOperation op, long delayUntilSeconds) {
    959         final long delayUntil = delayUntilSeconds * 1000;
    960         final long absoluteNow = System.currentTimeMillis();
    961         long newDelayUntilTime;
    962         if (delayUntil > absoluteNow) {
    963             newDelayUntilTime = SystemClock.elapsedRealtime() + (delayUntil - absoluteNow);
    964         } else {
    965             newDelayUntilTime = 0;
    966         }
    967         mSyncStorageEngine.setDelayUntilTime(op.target, newDelayUntilTime);
    968         synchronized (mSyncQueue) {
    969             mSyncQueue.onDelayUntilTimeChanged(op.target, newDelayUntilTime);
    970         }
    971     }
    972 
    973     /**
    974      * Cancel the active sync if it matches the target.
    975      * @param info object containing info about which syncs to cancel. The target can
    976      * have null account/provider info to specify all accounts/providers.
    977      * @param extras if non-null, specifies the exact sync to remove.
    978      */
    979     public void cancelActiveSync(SyncStorageEngine.EndPoint info, Bundle extras) {
    980         sendCancelSyncsMessage(info, extras);
    981     }
    982 
    983     /**
    984      * Create and schedule a SyncOperation.
    985      *
    986      * @param syncOperation the SyncOperation to schedule
    987      */
    988     public void scheduleSyncOperation(SyncOperation syncOperation) {
    989         boolean queueChanged;
    990         synchronized (mSyncQueue) {
    991             queueChanged = mSyncQueue.add(syncOperation);
    992         }
    993 
    994         if (queueChanged) {
    995             if (Log.isLoggable(TAG, Log.VERBOSE)) {
    996                 Log.v(TAG, "scheduleSyncOperation: enqueued " + syncOperation);
    997             }
    998             sendCheckAlarmsMessage();
    999         } else {
   1000             if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1001                 Log.v(TAG, "scheduleSyncOperation: dropping duplicate sync operation "
   1002                         + syncOperation);
   1003             }
   1004         }
   1005     }
   1006 
   1007     /**
   1008      * Remove scheduled sync operations.
   1009      * @param info limit the removals to operations that match this target. The target can
   1010      * have null account/provider info to specify all accounts/providers.
   1011      */
   1012     public void clearScheduledSyncOperations(SyncStorageEngine.EndPoint info) {
   1013         synchronized (mSyncQueue) {
   1014             mSyncQueue.remove(info, null /* all operations */);
   1015         }
   1016         mSyncStorageEngine.setBackoff(info,
   1017                 SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
   1018     }
   1019 
   1020     /**
   1021      * Remove a specified sync, if it exists.
   1022      * @param info Authority for which the sync is to be removed.
   1023      * @param extras extras bundle to uniquely identify sync.
   1024      */
   1025     public void cancelScheduledSyncOperation(SyncStorageEngine.EndPoint info, Bundle extras) {
   1026         synchronized (mSyncQueue) {
   1027             mSyncQueue.remove(info, extras);
   1028         }
   1029         // Reset the back-off if there are no more syncs pending.
   1030         if (!mSyncStorageEngine.isSyncPending(info)) {
   1031             mSyncStorageEngine.setBackoff(info,
   1032                     SyncStorageEngine.NOT_IN_BACKOFF_MODE, SyncStorageEngine.NOT_IN_BACKOFF_MODE);
   1033         }
   1034     }
   1035 
   1036     void maybeRescheduleSync(SyncResult syncResult, SyncOperation operation) {
   1037         boolean isLoggable = Log.isLoggable(TAG, Log.DEBUG);
   1038         if (isLoggable) {
   1039             Log.d(TAG, "encountered error(s) during the sync: " + syncResult + ", " + operation);
   1040         }
   1041 
   1042         operation = new SyncOperation(operation, 0L /* newRunTimeFromNow */);
   1043 
   1044         // The SYNC_EXTRAS_IGNORE_BACKOFF only applies to the first attempt to sync a given
   1045         // request. Retries of the request will always honor the backoff, so clear the
   1046         // flag in case we retry this request.
   1047         if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false)) {
   1048             operation.extras.remove(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF);
   1049         }
   1050 
   1051         if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false)) {
   1052             if (isLoggable) {
   1053                 Log.d(TAG, "not retrying sync operation because SYNC_EXTRAS_DO_NOT_RETRY was specified "
   1054                         + operation);
   1055             }
   1056         } else if (operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, false)
   1057                 && !syncResult.syncAlreadyInProgress) {
   1058             // If this was an upward sync then schedule a two-way sync immediately.
   1059             operation.extras.remove(ContentResolver.SYNC_EXTRAS_UPLOAD);
   1060             if (isLoggable) {
   1061                 Log.d(TAG, "retrying sync operation as a two-way sync because an upload-only sync "
   1062                         + "encountered an error: " + operation);
   1063             }
   1064             scheduleSyncOperation(operation);
   1065         } else if (syncResult.tooManyRetries) {
   1066             // If this sync aborted because the internal sync loop retried too many times then
   1067             //   don't reschedule. Otherwise we risk getting into a retry loop.
   1068             if (isLoggable) {
   1069                 Log.d(TAG, "not retrying sync operation because it retried too many times: "
   1070                         + operation);
   1071             }
   1072         } else if (syncResult.madeSomeProgress()) {
   1073             // If the operation succeeded to some extent then retry immediately.
   1074             if (isLoggable) {
   1075                 Log.d(TAG, "retrying sync operation because even though it had an error "
   1076                         + "it achieved some success");
   1077             }
   1078             scheduleSyncOperation(operation);
   1079         } else if (syncResult.syncAlreadyInProgress) {
   1080             if (isLoggable) {
   1081                 Log.d(TAG, "retrying sync operation that failed because there was already a "
   1082                         + "sync in progress: " + operation);
   1083             }
   1084             scheduleSyncOperation(
   1085                 new SyncOperation(
   1086                         operation,
   1087                         DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000 /* newRunTimeFromNow */)
   1088                 );
   1089         } else if (syncResult.hasSoftError()) {
   1090             // If this was a two-way sync then retry soft errors with an exponential backoff.
   1091             if (isLoggable) {
   1092                 Log.d(TAG, "retrying sync operation because it encountered a soft error: "
   1093                         + operation);
   1094             }
   1095             scheduleSyncOperation(operation);
   1096         } else {
   1097             // Otherwise do not reschedule.
   1098             Log.d(TAG, "not retrying sync operation because the error is a hard error: "
   1099                     + operation);
   1100         }
   1101     }
   1102 
   1103     private void onUserStarting(int userId) {
   1104         // Make sure that accounts we're about to use are valid
   1105         AccountManagerService.getSingleton().validateAccounts(userId);
   1106 
   1107         mSyncAdapters.invalidateCache(userId);
   1108 
   1109         updateRunningAccounts();
   1110 
   1111         synchronized (mSyncQueue) {
   1112             mSyncQueue.addPendingOperations(userId);
   1113         }
   1114 
   1115         // Schedule sync for any accounts under started user
   1116         final Account[] accounts = AccountManagerService.getSingleton().getAccounts(userId);
   1117         for (Account account : accounts) {
   1118             scheduleSync(account, userId, SyncOperation.REASON_USER_START, null, null,
   1119                     0 /* no delay */, 0 /* No flex */,
   1120                     true /* onlyThoseWithUnknownSyncableState */);
   1121         }
   1122 
   1123         sendCheckAlarmsMessage();
   1124     }
   1125 
   1126     private void onUserStopping(int userId) {
   1127         updateRunningAccounts();
   1128 
   1129         cancelActiveSync(
   1130                 new SyncStorageEngine.EndPoint(
   1131                         null /* any account */,
   1132                         null /* any authority */,
   1133                         userId),
   1134                         null /* any sync. */
   1135                 );
   1136     }
   1137 
   1138     private void onUserRemoved(int userId) {
   1139         updateRunningAccounts();
   1140 
   1141         // Clean up the storage engine database
   1142         mSyncStorageEngine.doDatabaseCleanup(new Account[0], userId);
   1143         synchronized (mSyncQueue) {
   1144             mSyncQueue.removeUserLocked(userId);
   1145         }
   1146     }
   1147 
   1148     /**
   1149      * @hide
   1150      */
   1151     class ActiveSyncContext extends ISyncContext.Stub
   1152             implements ServiceConnection, IBinder.DeathRecipient {
   1153         final SyncOperation mSyncOperation;
   1154         final long mHistoryRowId;
   1155         ISyncAdapter mSyncAdapter;
   1156         ISyncServiceAdapter mSyncServiceAdapter;
   1157         final long mStartTime;
   1158         long mTimeoutStartTime;
   1159         boolean mBound;
   1160         final PowerManager.WakeLock mSyncWakeLock;
   1161         final int mSyncAdapterUid;
   1162         SyncInfo mSyncInfo;
   1163         boolean mIsLinkedToDeath = false;
   1164         String mEventName;
   1165 
   1166         /**
   1167          * Create an ActiveSyncContext for an impending sync and grab the wakelock for that
   1168          * sync adapter. Since this grabs the wakelock you need to be sure to call
   1169          * close() when you are done with this ActiveSyncContext, whether the sync succeeded
   1170          * or not.
   1171          * @param syncOperation the SyncOperation we are about to sync
   1172          * @param historyRowId the row in which to record the history info for this sync
   1173          * @param syncAdapterUid the UID of the application that contains the sync adapter
   1174          * for this sync. This is used to attribute the wakelock hold to that application.
   1175          */
   1176         public ActiveSyncContext(SyncOperation syncOperation, long historyRowId,
   1177                 int syncAdapterUid) {
   1178             super();
   1179             mSyncAdapterUid = syncAdapterUid;
   1180             mSyncOperation = syncOperation;
   1181             mHistoryRowId = historyRowId;
   1182             mSyncAdapter = null;
   1183             mSyncServiceAdapter = null;
   1184             mStartTime = SystemClock.elapsedRealtime();
   1185             mTimeoutStartTime = mStartTime;
   1186             mSyncWakeLock = mSyncHandler.getSyncWakeLock(mSyncOperation);
   1187             mSyncWakeLock.setWorkSource(new WorkSource(syncAdapterUid));
   1188             mSyncWakeLock.acquire();
   1189         }
   1190 
   1191         public void sendHeartbeat() {
   1192             // heartbeats are no longer used
   1193         }
   1194 
   1195         public void onFinished(SyncResult result) {
   1196             if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "onFinished: " + this);
   1197             // include "this" in the message so that the handler can ignore it if this
   1198             // ActiveSyncContext is no longer the mActiveSyncContext at message handling
   1199             // time
   1200             sendSyncFinishedOrCanceledMessage(this, result);
   1201         }
   1202 
   1203         public void toString(StringBuilder sb) {
   1204             sb.append("startTime ").append(mStartTime)
   1205                     .append(", mTimeoutStartTime ").append(mTimeoutStartTime)
   1206                     .append(", mHistoryRowId ").append(mHistoryRowId)
   1207                     .append(", syncOperation ").append(mSyncOperation);
   1208         }
   1209 
   1210         public void onServiceConnected(ComponentName name, IBinder service) {
   1211             Message msg = mSyncHandler.obtainMessage();
   1212             msg.what = SyncHandler.MESSAGE_SERVICE_CONNECTED;
   1213             msg.obj = new ServiceConnectionData(this, service);
   1214             mSyncHandler.sendMessage(msg);
   1215         }
   1216 
   1217         public void onServiceDisconnected(ComponentName name) {
   1218             Message msg = mSyncHandler.obtainMessage();
   1219             msg.what = SyncHandler.MESSAGE_SERVICE_DISCONNECTED;
   1220             msg.obj = new ServiceConnectionData(this, null);
   1221             mSyncHandler.sendMessage(msg);
   1222         }
   1223 
   1224         boolean bindToSyncAdapter(ComponentName serviceComponent, int userId) {
   1225             if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1226                 Log.d(TAG, "bindToSyncAdapter: " + serviceComponent + ", connection " + this);
   1227             }
   1228             Intent intent = new Intent();
   1229             intent.setAction("android.content.SyncAdapter");
   1230             intent.setComponent(serviceComponent);
   1231             intent.putExtra(Intent.EXTRA_CLIENT_LABEL,
   1232                     com.android.internal.R.string.sync_binding_label);
   1233             intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivityAsUser(
   1234                     mContext, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0,
   1235                     null, new UserHandle(userId)));
   1236             mBound = true;
   1237             final boolean bindResult = mContext.bindServiceAsUser(intent, this,
   1238                     Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
   1239                     | Context.BIND_ALLOW_OOM_MANAGEMENT,
   1240                     new UserHandle(mSyncOperation.target.userId));
   1241             if (!bindResult) {
   1242                 mBound = false;
   1243             } else {
   1244                 try {
   1245                     mEventName = mSyncOperation.wakeLockName();
   1246                     mBatteryStats.noteSyncStart(mEventName, mSyncAdapterUid);
   1247                 } catch (RemoteException e) {
   1248                 }
   1249             }
   1250             return bindResult;
   1251         }
   1252 
   1253         /**
   1254          * Performs the required cleanup, which is the releasing of the wakelock and
   1255          * unbinding from the sync adapter (if actually bound).
   1256          */
   1257         protected void close() {
   1258             if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1259                 Log.d(TAG, "unBindFromSyncAdapter: connection " + this);
   1260             }
   1261             if (mBound) {
   1262                 mBound = false;
   1263                 mContext.unbindService(this);
   1264                 try {
   1265                     mBatteryStats.noteSyncFinish(mEventName, mSyncAdapterUid);
   1266                 } catch (RemoteException e) {
   1267                 }
   1268             }
   1269             mSyncWakeLock.release();
   1270             mSyncWakeLock.setWorkSource(null);
   1271         }
   1272 
   1273         public String toString() {
   1274             StringBuilder sb = new StringBuilder();
   1275             toString(sb);
   1276             return sb.toString();
   1277         }
   1278 
   1279         @Override
   1280         public void binderDied() {
   1281             sendSyncFinishedOrCanceledMessage(this, null);
   1282         }
   1283     }
   1284 
   1285     protected void dump(FileDescriptor fd, PrintWriter pw) {
   1286         final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
   1287         dumpSyncState(ipw);
   1288         dumpSyncHistory(ipw);
   1289         dumpSyncAdapters(ipw);
   1290     }
   1291 
   1292     static String formatTime(long time) {
   1293         Time tobj = new Time();
   1294         tobj.set(time);
   1295         return tobj.format("%Y-%m-%d %H:%M:%S");
   1296     }
   1297 
   1298     protected void dumpSyncState(PrintWriter pw) {
   1299         pw.print("data connected: "); pw.println(mDataConnectionIsConnected);
   1300         pw.print("auto sync: ");
   1301         List<UserInfo> users = getAllUsers();
   1302         if (users != null) {
   1303             for (UserInfo user : users) {
   1304                 pw.print("u" + user.id + "="
   1305                         + mSyncStorageEngine.getMasterSyncAutomatically(user.id) + " ");
   1306             }
   1307             pw.println();
   1308         }
   1309         pw.print("memory low: "); pw.println(mStorageIsLow);
   1310 
   1311         final AccountAndUser[] accounts = AccountManagerService.getSingleton().getAllAccounts();
   1312 
   1313         pw.print("accounts: ");
   1314         if (accounts != INITIAL_ACCOUNTS_ARRAY) {
   1315             pw.println(accounts.length);
   1316         } else {
   1317             pw.println("not known yet");
   1318         }
   1319         final long now = SystemClock.elapsedRealtime();
   1320         pw.print("now: "); pw.print(now);
   1321         pw.println(" (" + formatTime(System.currentTimeMillis()) + ")");
   1322         pw.print("offset: "); pw.print(DateUtils.formatElapsedTime(mSyncRandomOffsetMillis/1000));
   1323         pw.println(" (HH:MM:SS)");
   1324         pw.print("uptime: "); pw.print(DateUtils.formatElapsedTime(now/1000));
   1325                 pw.println(" (HH:MM:SS)");
   1326         pw.print("time spent syncing: ");
   1327                 pw.print(DateUtils.formatElapsedTime(
   1328                         mSyncHandler.mSyncTimeTracker.timeSpentSyncing() / 1000));
   1329                 pw.print(" (HH:MM:SS), sync ");
   1330                 pw.print(mSyncHandler.mSyncTimeTracker.mLastWasSyncing ? "" : "not ");
   1331                 pw.println("in progress");
   1332         if (mSyncHandler.mAlarmScheduleTime != null) {
   1333             pw.print("next alarm time: "); pw.print(mSyncHandler.mAlarmScheduleTime);
   1334                     pw.print(" (");
   1335                     pw.print(DateUtils.formatElapsedTime((mSyncHandler.mAlarmScheduleTime-now)/1000));
   1336                     pw.println(" (HH:MM:SS) from now)");
   1337         } else {
   1338             pw.println("no alarm is scheduled (there had better not be any pending syncs)");
   1339         }
   1340 
   1341         pw.print("notification info: ");
   1342         final StringBuilder sb = new StringBuilder();
   1343         mSyncHandler.mSyncNotificationInfo.toString(sb);
   1344         pw.println(sb.toString());
   1345 
   1346         pw.println();
   1347         pw.println("Active Syncs: " + mActiveSyncContexts.size());
   1348         final PackageManager pm = mContext.getPackageManager();
   1349         for (SyncManager.ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
   1350             final long durationInSeconds = (now - activeSyncContext.mStartTime) / 1000;
   1351             pw.print("  ");
   1352             pw.print(DateUtils.formatElapsedTime(durationInSeconds));
   1353             pw.print(" - ");
   1354             pw.print(activeSyncContext.mSyncOperation.dump(pm, false));
   1355             pw.println();
   1356         }
   1357 
   1358         synchronized (mSyncQueue) {
   1359             sb.setLength(0);
   1360             mSyncQueue.dump(sb);
   1361             // Dump Pending Operations.
   1362             getSyncStorageEngine().dumpPendingOperations(sb);
   1363         }
   1364 
   1365         pw.println();
   1366         pw.print(sb.toString());
   1367 
   1368         // join the installed sync adapter with the accounts list and emit for everything
   1369         pw.println();
   1370         pw.println("Sync Status");
   1371         for (AccountAndUser account : accounts) {
   1372             pw.printf("Account %s u%d %s\n",
   1373                     account.account.name, account.userId, account.account.type);
   1374 
   1375             pw.println("=======================================================================");
   1376             final PrintTable table = new PrintTable(13);
   1377             table.set(0, 0,
   1378                     "Authority", // 0
   1379                     "Syncable",  // 1
   1380                     "Enabled",   // 2
   1381                     "Delay",     // 3
   1382                     "Loc",       // 4
   1383                     "Poll",      // 5
   1384                     "Per",       // 6
   1385                     "Serv",      // 7
   1386                     "User",      // 8
   1387                     "Tot",       // 9
   1388                     "Time",      // 10
   1389                     "Last Sync", // 11
   1390                     "Periodic"   // 12
   1391             );
   1392 
   1393             final List<RegisteredServicesCache.ServiceInfo<SyncAdapterType>> sorted =
   1394                     Lists.newArrayList();
   1395             sorted.addAll(mSyncAdapters.getAllServices(account.userId));
   1396             Collections.sort(sorted,
   1397                     new Comparator<RegisteredServicesCache.ServiceInfo<SyncAdapterType>>() {
   1398                 @Override
   1399                 public int compare(RegisteredServicesCache.ServiceInfo<SyncAdapterType> lhs,
   1400                         RegisteredServicesCache.ServiceInfo<SyncAdapterType> rhs) {
   1401                     return lhs.type.authority.compareTo(rhs.type.authority);
   1402                 }
   1403             });
   1404             for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterType : sorted) {
   1405                 if (!syncAdapterType.type.accountType.equals(account.account.type)) {
   1406                     continue;
   1407                 }
   1408                 int row = table.getNumRows();
   1409                 Pair<AuthorityInfo, SyncStatusInfo> syncAuthoritySyncStatus =
   1410                         mSyncStorageEngine.getCopyOfAuthorityWithSyncStatus(
   1411                                 new SyncStorageEngine.EndPoint(
   1412                                         account.account,
   1413                                         syncAdapterType.type.authority,
   1414                                         account.userId));
   1415                 SyncStorageEngine.AuthorityInfo settings = syncAuthoritySyncStatus.first;
   1416                 SyncStatusInfo status = syncAuthoritySyncStatus.second;
   1417                 String authority = settings.target.provider;
   1418                 if (authority.length() > 50) {
   1419                     authority = authority.substring(authority.length() - 50);
   1420                 }
   1421                 table.set(row, 0, authority, settings.syncable, settings.enabled);
   1422                 table.set(row, 4,
   1423                         status.numSourceLocal,
   1424                         status.numSourcePoll,
   1425                         status.numSourcePeriodic,
   1426                         status.numSourceServer,
   1427                         status.numSourceUser,
   1428                         status.numSyncs,
   1429                         DateUtils.formatElapsedTime(status.totalElapsedTime / 1000));
   1430 
   1431 
   1432                 for (int i = 0; i < settings.periodicSyncs.size(); i++) {
   1433                     final PeriodicSync sync = settings.periodicSyncs.get(i);
   1434                     final String period =
   1435                             String.format("[p:%d s, f: %d s]", sync.period, sync.flexTime);
   1436                     final String extras =
   1437                             sync.extras.size() > 0 ?
   1438                                     sync.extras.toString() : "Bundle[]";
   1439                     final String next = "Next sync: " + formatTime(status.getPeriodicSyncTime(i)
   1440                             + sync.period * 1000);
   1441                     table.set(row + i * 2, 12, period + " " + extras);
   1442                     table.set(row + i * 2 + 1, 12, next);
   1443                 }
   1444 
   1445                 int row1 = row;
   1446                 if (settings.delayUntil > now) {
   1447                     table.set(row1++, 12, "D: " + (settings.delayUntil - now) / 1000);
   1448                     if (settings.backoffTime > now) {
   1449                         table.set(row1++, 12, "B: " + (settings.backoffTime - now) / 1000);
   1450                         table.set(row1++, 12, settings.backoffDelay / 1000);
   1451                     }
   1452                 }
   1453 
   1454                 if (status.lastSuccessTime != 0) {
   1455                     table.set(row1++, 11, SyncStorageEngine.SOURCES[status.lastSuccessSource]
   1456                             + " " + "SUCCESS");
   1457                     table.set(row1++, 11, formatTime(status.lastSuccessTime));
   1458                 }
   1459                 if (status.lastFailureTime != 0) {
   1460                     table.set(row1++, 11, SyncStorageEngine.SOURCES[status.lastFailureSource]
   1461                             + " " + "FAILURE");
   1462                     table.set(row1++, 11, formatTime(status.lastFailureTime));
   1463                     //noinspection UnusedAssignment
   1464                     table.set(row1++, 11, status.lastFailureMesg);
   1465                 }
   1466             }
   1467             table.writeTo(pw);
   1468         }
   1469     }
   1470 
   1471     private String getLastFailureMessage(int code) {
   1472         switch (code) {
   1473             case ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS:
   1474                 return "sync already in progress";
   1475 
   1476             case ContentResolver.SYNC_ERROR_AUTHENTICATION:
   1477                 return "authentication error";
   1478 
   1479             case ContentResolver.SYNC_ERROR_IO:
   1480                 return "I/O error";
   1481 
   1482             case ContentResolver.SYNC_ERROR_PARSE:
   1483                 return "parse error";
   1484 
   1485             case ContentResolver.SYNC_ERROR_CONFLICT:
   1486                 return "conflict error";
   1487 
   1488             case ContentResolver.SYNC_ERROR_TOO_MANY_DELETIONS:
   1489                 return "too many deletions error";
   1490 
   1491             case ContentResolver.SYNC_ERROR_TOO_MANY_RETRIES:
   1492                 return "too many retries error";
   1493 
   1494             case ContentResolver.SYNC_ERROR_INTERNAL:
   1495                 return "internal error";
   1496 
   1497             default:
   1498                 return "unknown";
   1499         }
   1500     }
   1501 
   1502     private void dumpTimeSec(PrintWriter pw, long time) {
   1503         pw.print(time/1000); pw.print('.'); pw.print((time/100)%10);
   1504         pw.print('s');
   1505     }
   1506 
   1507     private void dumpDayStatistic(PrintWriter pw, SyncStorageEngine.DayStats ds) {
   1508         pw.print("Success ("); pw.print(ds.successCount);
   1509         if (ds.successCount > 0) {
   1510             pw.print(" for "); dumpTimeSec(pw, ds.successTime);
   1511             pw.print(" avg="); dumpTimeSec(pw, ds.successTime/ds.successCount);
   1512         }
   1513         pw.print(") Failure ("); pw.print(ds.failureCount);
   1514         if (ds.failureCount > 0) {
   1515             pw.print(" for "); dumpTimeSec(pw, ds.failureTime);
   1516             pw.print(" avg="); dumpTimeSec(pw, ds.failureTime/ds.failureCount);
   1517         }
   1518         pw.println(")");
   1519     }
   1520 
   1521     protected void dumpSyncHistory(PrintWriter pw) {
   1522         dumpRecentHistory(pw);
   1523         dumpDayStatistics(pw);
   1524     }
   1525 
   1526     private void dumpRecentHistory(PrintWriter pw) {
   1527         final ArrayList<SyncStorageEngine.SyncHistoryItem> items
   1528                 = mSyncStorageEngine.getSyncHistory();
   1529         if (items != null && items.size() > 0) {
   1530             final Map<String, AuthoritySyncStats> authorityMap = Maps.newHashMap();
   1531             long totalElapsedTime = 0;
   1532             long totalTimes = 0;
   1533             final int N = items.size();
   1534 
   1535             int maxAuthority = 0;
   1536             int maxAccount = 0;
   1537             for (SyncStorageEngine.SyncHistoryItem item : items) {
   1538                 SyncStorageEngine.AuthorityInfo authorityInfo
   1539                         = mSyncStorageEngine.getAuthority(item.authorityId);
   1540                 final String authorityName;
   1541                 final String accountKey;
   1542                 if (authorityInfo != null) {
   1543                     if (authorityInfo.target.target_provider) {
   1544                         authorityName = authorityInfo.target.provider;
   1545                         accountKey = authorityInfo.target.account.name + "/"
   1546                                 + authorityInfo.target.account.type
   1547                                 + " u" + authorityInfo.target.userId;
   1548                     } else if (authorityInfo.target.target_service) {
   1549                         authorityName = authorityInfo.target.service.getPackageName() + "/"
   1550                                 + authorityInfo.target.service.getClassName()
   1551                                 + " u" + authorityInfo.target.userId;
   1552                         accountKey = "no account";
   1553                     } else {
   1554                         authorityName = "Unknown";
   1555                         accountKey = "Unknown";
   1556                     }
   1557                 } else {
   1558                     authorityName = "Unknown";
   1559                     accountKey = "Unknown";
   1560                 }
   1561 
   1562                 int length = authorityName.length();
   1563                 if (length > maxAuthority) {
   1564                     maxAuthority = length;
   1565                 }
   1566                 length = accountKey.length();
   1567                 if (length > maxAccount) {
   1568                     maxAccount = length;
   1569                 }
   1570 
   1571                 final long elapsedTime = item.elapsedTime;
   1572                 totalElapsedTime += elapsedTime;
   1573                 totalTimes++;
   1574                 AuthoritySyncStats authoritySyncStats = authorityMap.get(authorityName);
   1575                 if (authoritySyncStats == null) {
   1576                     authoritySyncStats = new AuthoritySyncStats(authorityName);
   1577                     authorityMap.put(authorityName, authoritySyncStats);
   1578                 }
   1579                 authoritySyncStats.elapsedTime += elapsedTime;
   1580                 authoritySyncStats.times++;
   1581                 final Map<String, AccountSyncStats> accountMap = authoritySyncStats.accountMap;
   1582                 AccountSyncStats accountSyncStats = accountMap.get(accountKey);
   1583                 if (accountSyncStats == null) {
   1584                     accountSyncStats = new AccountSyncStats(accountKey);
   1585                     accountMap.put(accountKey, accountSyncStats);
   1586                 }
   1587                 accountSyncStats.elapsedTime += elapsedTime;
   1588                 accountSyncStats.times++;
   1589 
   1590             }
   1591 
   1592             if (totalElapsedTime > 0) {
   1593                 pw.println();
   1594                 pw.printf("Detailed Statistics (Recent history):  "
   1595                         + "%d (# of times) %ds (sync time)\n",
   1596                         totalTimes, totalElapsedTime / 1000);
   1597 
   1598                 final List<AuthoritySyncStats> sortedAuthorities =
   1599                         new ArrayList<AuthoritySyncStats>(authorityMap.values());
   1600                 Collections.sort(sortedAuthorities, new Comparator<AuthoritySyncStats>() {
   1601                     @Override
   1602                     public int compare(AuthoritySyncStats lhs, AuthoritySyncStats rhs) {
   1603                         // reverse order
   1604                         int compare = Integer.compare(rhs.times, lhs.times);
   1605                         if (compare == 0) {
   1606                             compare = Long.compare(rhs.elapsedTime, lhs.elapsedTime);
   1607                         }
   1608                         return compare;
   1609                     }
   1610                 });
   1611 
   1612                 final int maxLength = Math.max(maxAuthority, maxAccount + 3);
   1613                 final int padLength = 2 + 2 + maxLength + 2 + 10 + 11;
   1614                 final char chars[] = new char[padLength];
   1615                 Arrays.fill(chars, '-');
   1616                 final String separator = new String(chars);
   1617 
   1618                 final String authorityFormat =
   1619                         String.format("  %%-%ds: %%-9s  %%-11s\n", maxLength + 2);
   1620                 final String accountFormat =
   1621                         String.format("    %%-%ds:   %%-9s  %%-11s\n", maxLength);
   1622 
   1623                 pw.println(separator);
   1624                 for (AuthoritySyncStats authoritySyncStats : sortedAuthorities) {
   1625                     String name = authoritySyncStats.name;
   1626                     long elapsedTime;
   1627                     int times;
   1628                     String timeStr;
   1629                     String timesStr;
   1630 
   1631                     elapsedTime = authoritySyncStats.elapsedTime;
   1632                     times = authoritySyncStats.times;
   1633                     timeStr = String.format("%ds/%d%%",
   1634                             elapsedTime / 1000,
   1635                             elapsedTime * 100 / totalElapsedTime);
   1636                     timesStr = String.format("%d/%d%%",
   1637                             times,
   1638                             times * 100 / totalTimes);
   1639                     pw.printf(authorityFormat, name, timesStr, timeStr);
   1640 
   1641                     final List<AccountSyncStats> sortedAccounts =
   1642                             new ArrayList<AccountSyncStats>(
   1643                                     authoritySyncStats.accountMap.values());
   1644                     Collections.sort(sortedAccounts, new Comparator<AccountSyncStats>() {
   1645                         @Override
   1646                         public int compare(AccountSyncStats lhs, AccountSyncStats rhs) {
   1647                             // reverse order
   1648                             int compare = Integer.compare(rhs.times, lhs.times);
   1649                             if (compare == 0) {
   1650                                 compare = Long.compare(rhs.elapsedTime, lhs.elapsedTime);
   1651                             }
   1652                             return compare;
   1653                         }
   1654                     });
   1655                     for (AccountSyncStats stats: sortedAccounts) {
   1656                         elapsedTime = stats.elapsedTime;
   1657                         times = stats.times;
   1658                         timeStr = String.format("%ds/%d%%",
   1659                                 elapsedTime / 1000,
   1660                                 elapsedTime * 100 / totalElapsedTime);
   1661                         timesStr = String.format("%d/%d%%",
   1662                                 times,
   1663                                 times * 100 / totalTimes);
   1664                         pw.printf(accountFormat, stats.name, timesStr, timeStr);
   1665                     }
   1666                     pw.println(separator);
   1667                 }
   1668             }
   1669 
   1670             pw.println();
   1671             pw.println("Recent Sync History");
   1672             final String format = "  %-" + maxAccount + "s  %-" + maxAuthority + "s %s\n";
   1673             final Map<String, Long> lastTimeMap = Maps.newHashMap();
   1674             final PackageManager pm = mContext.getPackageManager();
   1675             for (int i = 0; i < N; i++) {
   1676                 SyncStorageEngine.SyncHistoryItem item = items.get(i);
   1677                 SyncStorageEngine.AuthorityInfo authorityInfo
   1678                         = mSyncStorageEngine.getAuthority(item.authorityId);
   1679                 final String authorityName;
   1680                 final String accountKey;
   1681                 if (authorityInfo != null) {
   1682                     if (authorityInfo.target.target_provider) {
   1683                         authorityName = authorityInfo.target.provider;
   1684                         accountKey = authorityInfo.target.account.name + "/"
   1685                                 + authorityInfo.target.account.type
   1686                                 + " u" + authorityInfo.target.userId;
   1687                     } else if (authorityInfo.target.target_service) {
   1688                         authorityName = authorityInfo.target.service.getPackageName() + "/"
   1689                                 + authorityInfo.target.service.getClassName()
   1690                                 + " u" + authorityInfo.target.userId;
   1691                         accountKey = "none";
   1692                     } else {
   1693                         authorityName = "Unknown";
   1694                         accountKey = "Unknown";
   1695                     }
   1696                 } else {
   1697                     authorityName = "Unknown";
   1698                     accountKey = "Unknown";
   1699                 }
   1700                 final long elapsedTime = item.elapsedTime;
   1701                 final Time time = new Time();
   1702                 final long eventTime = item.eventTime;
   1703                 time.set(eventTime);
   1704 
   1705                 final String key = authorityName + "/" + accountKey;
   1706                 final Long lastEventTime = lastTimeMap.get(key);
   1707                 final String diffString;
   1708                 if (lastEventTime == null) {
   1709                     diffString = "";
   1710                 } else {
   1711                     final long diff = (lastEventTime - eventTime) / 1000;
   1712                     if (diff < 60) {
   1713                         diffString = String.valueOf(diff);
   1714                     } else if (diff < 3600) {
   1715                         diffString = String.format("%02d:%02d", diff / 60, diff % 60);
   1716                     } else {
   1717                         final long sec = diff % 3600;
   1718                         diffString = String.format("%02d:%02d:%02d",
   1719                                 diff / 3600, sec / 60, sec % 60);
   1720                     }
   1721                 }
   1722                 lastTimeMap.put(key, eventTime);
   1723 
   1724                 pw.printf("  #%-3d: %s %8s  %5.1fs  %8s",
   1725                         i + 1,
   1726                         formatTime(eventTime),
   1727                         SyncStorageEngine.SOURCES[item.source],
   1728                         ((float) elapsedTime) / 1000,
   1729                         diffString);
   1730                 pw.printf(format, accountKey, authorityName,
   1731                         SyncOperation.reasonToString(pm, item.reason));
   1732 
   1733                 if (item.event != SyncStorageEngine.EVENT_STOP
   1734                         || item.upstreamActivity != 0
   1735                         || item.downstreamActivity != 0) {
   1736                     pw.printf("    event=%d upstreamActivity=%d downstreamActivity=%d\n",
   1737                             item.event,
   1738                             item.upstreamActivity,
   1739                             item.downstreamActivity);
   1740                 }
   1741                 if (item.mesg != null
   1742                         && !SyncStorageEngine.MESG_SUCCESS.equals(item.mesg)) {
   1743                     pw.printf("    mesg=%s\n", item.mesg);
   1744                 }
   1745             }
   1746             pw.println();
   1747             pw.println("Recent Sync History Extras");
   1748             for (int i = 0; i < N; i++) {
   1749                 final SyncStorageEngine.SyncHistoryItem item = items.get(i);
   1750                 final Bundle extras = item.extras;
   1751                 if (extras == null || extras.size() == 0) {
   1752                     continue;
   1753                 }
   1754                 final SyncStorageEngine.AuthorityInfo authorityInfo
   1755                         = mSyncStorageEngine.getAuthority(item.authorityId);
   1756                 final String authorityName;
   1757                 final String accountKey;
   1758                 if (authorityInfo != null) {
   1759                     if (authorityInfo.target.target_provider) {
   1760                         authorityName = authorityInfo.target.provider;
   1761                         accountKey = authorityInfo.target.account.name + "/"
   1762                                 + authorityInfo.target.account.type
   1763                                 + " u" + authorityInfo.target.userId;
   1764                     } else if (authorityInfo.target.target_service) {
   1765                         authorityName = authorityInfo.target.service.getPackageName() + "/"
   1766                                 + authorityInfo.target.service.getClassName()
   1767                                 + " u" + authorityInfo.target.userId;
   1768                         accountKey = "none";
   1769                     } else {
   1770                         authorityName = "Unknown";
   1771                         accountKey = "Unknown";
   1772                     }
   1773                 } else {
   1774                     authorityName = "Unknown";
   1775                     accountKey = "Unknown";
   1776                 }
   1777                 final Time time = new Time();
   1778                 final long eventTime = item.eventTime;
   1779                 time.set(eventTime);
   1780 
   1781                 pw.printf("  #%-3d: %s %8s ",
   1782                         i + 1,
   1783                         formatTime(eventTime),
   1784                         SyncStorageEngine.SOURCES[item.source]);
   1785 
   1786                 pw.printf(format, accountKey, authorityName, extras);
   1787             }
   1788         }
   1789     }
   1790 
   1791     private void dumpDayStatistics(PrintWriter pw) {
   1792         SyncStorageEngine.DayStats dses[] = mSyncStorageEngine.getDayStatistics();
   1793         if (dses != null && dses[0] != null) {
   1794             pw.println();
   1795             pw.println("Sync Statistics");
   1796             pw.print("  Today:  "); dumpDayStatistic(pw, dses[0]);
   1797             int today = dses[0].day;
   1798             int i;
   1799             SyncStorageEngine.DayStats ds;
   1800 
   1801             // Print each day in the current week.
   1802             for (i=1; i<=6 && i < dses.length; i++) {
   1803                 ds = dses[i];
   1804                 if (ds == null) break;
   1805                 int delta = today-ds.day;
   1806                 if (delta > 6) break;
   1807 
   1808                 pw.print("  Day-"); pw.print(delta); pw.print(":  ");
   1809                 dumpDayStatistic(pw, ds);
   1810             }
   1811 
   1812             // Aggregate all following days into weeks and print totals.
   1813             int weekDay = today;
   1814             while (i < dses.length) {
   1815                 SyncStorageEngine.DayStats aggr = null;
   1816                 weekDay -= 7;
   1817                 while (i < dses.length) {
   1818                     ds = dses[i];
   1819                     if (ds == null) {
   1820                         i = dses.length;
   1821                         break;
   1822                     }
   1823                     int delta = weekDay-ds.day;
   1824                     if (delta > 6) break;
   1825                     i++;
   1826 
   1827                     if (aggr == null) {
   1828                         aggr = new SyncStorageEngine.DayStats(weekDay);
   1829                     }
   1830                     aggr.successCount += ds.successCount;
   1831                     aggr.successTime += ds.successTime;
   1832                     aggr.failureCount += ds.failureCount;
   1833                     aggr.failureTime += ds.failureTime;
   1834                 }
   1835                 if (aggr != null) {
   1836                     pw.print("  Week-"); pw.print((today-weekDay)/7); pw.print(": ");
   1837                     dumpDayStatistic(pw, aggr);
   1838                 }
   1839             }
   1840         }
   1841     }
   1842 
   1843     private void dumpSyncAdapters(IndentingPrintWriter pw) {
   1844         pw.println();
   1845         final List<UserInfo> users = getAllUsers();
   1846         if (users != null) {
   1847             for (UserInfo user : users) {
   1848                 pw.println("Sync adapters for " + user + ":");
   1849                 pw.increaseIndent();
   1850                 for (RegisteredServicesCache.ServiceInfo<?> info :
   1851                         mSyncAdapters.getAllServices(user.id)) {
   1852                     pw.println(info);
   1853                 }
   1854                 pw.decreaseIndent();
   1855                 pw.println();
   1856             }
   1857         }
   1858     }
   1859 
   1860     private static class AuthoritySyncStats {
   1861         String name;
   1862         long elapsedTime;
   1863         int times;
   1864         Map<String, AccountSyncStats> accountMap = Maps.newHashMap();
   1865 
   1866         private AuthoritySyncStats(String name) {
   1867             this.name = name;
   1868         }
   1869     }
   1870 
   1871     private static class AccountSyncStats {
   1872         String name;
   1873         long elapsedTime;
   1874         int times;
   1875 
   1876         private AccountSyncStats(String name) {
   1877             this.name = name;
   1878         }
   1879     }
   1880 
   1881     /**
   1882      * A helper object to keep track of the time we have spent syncing since the last boot
   1883      */
   1884     private class SyncTimeTracker {
   1885         /** True if a sync was in progress on the most recent call to update() */
   1886         boolean mLastWasSyncing = false;
   1887         /** Used to track when lastWasSyncing was last set */
   1888         long mWhenSyncStarted = 0;
   1889         /** The cumulative time we have spent syncing */
   1890         private long mTimeSpentSyncing;
   1891 
   1892         /** Call to let the tracker know that the sync state may have changed */
   1893         public synchronized void update() {
   1894             final boolean isSyncInProgress = !mActiveSyncContexts.isEmpty();
   1895             if (isSyncInProgress == mLastWasSyncing) return;
   1896             final long now = SystemClock.elapsedRealtime();
   1897             if (isSyncInProgress) {
   1898                 mWhenSyncStarted = now;
   1899             } else {
   1900                 mTimeSpentSyncing += now - mWhenSyncStarted;
   1901             }
   1902             mLastWasSyncing = isSyncInProgress;
   1903         }
   1904 
   1905         /** Get how long we have been syncing, in ms */
   1906         public synchronized long timeSpentSyncing() {
   1907             if (!mLastWasSyncing) return mTimeSpentSyncing;
   1908 
   1909             final long now = SystemClock.elapsedRealtime();
   1910             return mTimeSpentSyncing + (now - mWhenSyncStarted);
   1911         }
   1912     }
   1913 
   1914     class ServiceConnectionData {
   1915         public final ActiveSyncContext activeSyncContext;
   1916         public final IBinder adapter;
   1917 
   1918         ServiceConnectionData(ActiveSyncContext activeSyncContext, IBinder adapter) {
   1919             this.activeSyncContext = activeSyncContext;
   1920             this.adapter = adapter;
   1921         }
   1922     }
   1923 
   1924     /**
   1925      * Handles SyncOperation Messages that are posted to the associated
   1926      * HandlerThread.
   1927      */
   1928     class SyncHandler extends Handler {
   1929         // Messages that can be sent on mHandler
   1930         private static final int MESSAGE_SYNC_FINISHED = 1;
   1931         private static final int MESSAGE_SYNC_ALARM = 2;
   1932         private static final int MESSAGE_CHECK_ALARMS = 3;
   1933         private static final int MESSAGE_SERVICE_CONNECTED = 4;
   1934         private static final int MESSAGE_SERVICE_DISCONNECTED = 5;
   1935         private static final int MESSAGE_CANCEL = 6;
   1936         /** Posted delayed in order to expire syncs that are long-running. */
   1937         private static final int MESSAGE_SYNC_EXPIRED = 7;
   1938 
   1939         public final SyncNotificationInfo mSyncNotificationInfo = new SyncNotificationInfo();
   1940         private Long mAlarmScheduleTime = null;
   1941         public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker();
   1942         private final HashMap<String, PowerManager.WakeLock> mWakeLocks = Maps.newHashMap();
   1943 
   1944         private List<Message> mBootQueue = new ArrayList<Message>();
   1945 
   1946       public void onBootCompleted() {
   1947             if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1948                 Log.v(TAG, "Boot completed, clearing boot queue.");
   1949             }
   1950             doDatabaseCleanup();
   1951             synchronized(this) {
   1952                 // Dispatch any stashed messages.
   1953                 for (Message message : mBootQueue) {
   1954                     sendMessage(message);
   1955                 }
   1956                 mBootQueue = null;
   1957                 mBootCompleted = true;
   1958             }
   1959         }
   1960 
   1961         private PowerManager.WakeLock getSyncWakeLock(SyncOperation operation) {
   1962             final String wakeLockKey = operation.wakeLockName();
   1963             PowerManager.WakeLock wakeLock = mWakeLocks.get(wakeLockKey);
   1964             if (wakeLock == null) {
   1965                 final String name = SYNC_WAKE_LOCK_PREFIX + wakeLockKey;
   1966                 wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
   1967                 wakeLock.setReferenceCounted(false);
   1968                 mWakeLocks.put(wakeLockKey, wakeLock);
   1969             }
   1970             return wakeLock;
   1971         }
   1972 
   1973         /**
   1974          * Stash any messages that come to the handler before boot is complete.
   1975          * {@link #onBootCompleted()} will disable this and dispatch all the messages collected.
   1976          * @param msg Message to dispatch at a later point.
   1977          * @return true if a message was enqueued, false otherwise. This is to avoid losing the
   1978          * message if we manage to acquire the lock but by the time we do boot has completed.
   1979          */
   1980         private boolean tryEnqueueMessageUntilReadyToRun(Message msg) {
   1981             synchronized (this) {
   1982                 if (!mBootCompleted) {
   1983                     // Need to copy the message bc looper will recycle it.
   1984                     mBootQueue.add(Message.obtain(msg));
   1985                     return true;
   1986                 }
   1987                 return false;
   1988             }
   1989         }
   1990 
   1991         /**
   1992          * Used to keep track of whether a sync notification is active and who it is for.
   1993          */
   1994         class SyncNotificationInfo {
   1995             // true iff the notification manager has been asked to send the notification
   1996             public boolean isActive = false;
   1997 
   1998             // Set when we transition from not running a sync to running a sync, and cleared on
   1999             // the opposite transition.
   2000             public Long startTime = null;
   2001 
   2002             public void toString(StringBuilder sb) {
   2003                 sb.append("isActive ").append(isActive).append(", startTime ").append(startTime);
   2004             }
   2005 
   2006             @Override
   2007             public String toString() {
   2008                 StringBuilder sb = new StringBuilder();
   2009                 toString(sb);
   2010                 return sb.toString();
   2011             }
   2012         }
   2013 
   2014         public SyncHandler(Looper looper) {
   2015             super(looper);
   2016         }
   2017 
   2018         public void handleMessage(Message msg) {
   2019             if (tryEnqueueMessageUntilReadyToRun(msg)) {
   2020                 return;
   2021             }
   2022 
   2023             long earliestFuturePollTime = Long.MAX_VALUE;
   2024             long nextPendingSyncTime = Long.MAX_VALUE;
   2025             // Setting the value here instead of a method because we want the dumpsys logs
   2026             // to have the most recent value used.
   2027             try {
   2028                 mDataConnectionIsConnected = readDataConnectionState();
   2029                 mSyncManagerWakeLock.acquire();
   2030                 // Always do this first so that we be sure that any periodic syncs that
   2031                 // are ready to run have been converted into pending syncs. This allows the
   2032                 // logic that considers the next steps to take based on the set of pending syncs
   2033                 // to also take into account the periodic syncs.
   2034                 earliestFuturePollTime = scheduleReadyPeriodicSyncs();
   2035                 switch (msg.what) {
   2036                     case SyncHandler.MESSAGE_SYNC_EXPIRED:
   2037                         ActiveSyncContext expiredContext = (ActiveSyncContext) msg.obj;
   2038                         if (Log.isLoggable(TAG, Log.DEBUG)) {
   2039                             Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_EXPIRED: expiring "
   2040                                     + expiredContext);
   2041                         }
   2042                         cancelActiveSync(expiredContext.mSyncOperation.target,
   2043                                 expiredContext.mSyncOperation.extras);
   2044                         nextPendingSyncTime = maybeStartNextSyncLocked();
   2045                         break;
   2046 
   2047                     case SyncHandler.MESSAGE_CANCEL: {
   2048                         SyncStorageEngine.EndPoint payload = (SyncStorageEngine.EndPoint) msg.obj;
   2049                         Bundle extras = msg.peekData();
   2050                         if (Log.isLoggable(TAG, Log.DEBUG)) {
   2051                             Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CANCEL: "
   2052                                     + payload + " bundle: " + extras);
   2053                         }
   2054                         cancelActiveSyncLocked(payload, extras);
   2055                         nextPendingSyncTime = maybeStartNextSyncLocked();
   2056                         break;
   2057                     }
   2058 
   2059                     case SyncHandler.MESSAGE_SYNC_FINISHED:
   2060                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
   2061                             Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_FINISHED");
   2062                         }
   2063                         SyncHandlerMessagePayload payload = (SyncHandlerMessagePayload) msg.obj;
   2064                         if (!isSyncStillActive(payload.activeSyncContext)) {
   2065                             Log.d(TAG, "handleSyncHandlerMessage: dropping since the "
   2066                                     + "sync is no longer active: "
   2067                                     + payload.activeSyncContext);
   2068                             break;
   2069                         }
   2070                         runSyncFinishedOrCanceledLocked(payload.syncResult,
   2071                                 payload.activeSyncContext);
   2072 
   2073                         // since a sync just finished check if it is time to start a new sync
   2074                         nextPendingSyncTime = maybeStartNextSyncLocked();
   2075                         break;
   2076 
   2077                     case SyncHandler.MESSAGE_SERVICE_CONNECTED: {
   2078                         ServiceConnectionData msgData = (ServiceConnectionData) msg.obj;
   2079                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
   2080                             Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CONNECTED: "
   2081                                     + msgData.activeSyncContext);
   2082                         }
   2083                         // check that this isn't an old message
   2084                         if (isSyncStillActive(msgData.activeSyncContext)) {
   2085                             runBoundToAdapter(
   2086                                     msgData.activeSyncContext,
   2087                                     msgData.adapter);
   2088                         }
   2089                         break;
   2090                     }
   2091 
   2092                     case SyncHandler.MESSAGE_SERVICE_DISCONNECTED: {
   2093                         final ActiveSyncContext currentSyncContext =
   2094                                 ((ServiceConnectionData) msg.obj).activeSyncContext;
   2095                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
   2096                             Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_DISCONNECTED: "
   2097                                     + currentSyncContext);
   2098                         }
   2099                         // check that this isn't an old message
   2100                         if (isSyncStillActive(currentSyncContext)) {
   2101                             // cancel the sync if we have a syncadapter, which means one is
   2102                             // outstanding
   2103                             try {
   2104                                 if (currentSyncContext.mSyncAdapter != null) {
   2105                                     currentSyncContext.mSyncAdapter.cancelSync(currentSyncContext);
   2106                                 } else if (currentSyncContext.mSyncServiceAdapter != null) {
   2107                                     currentSyncContext.mSyncServiceAdapter
   2108                                         .cancelSync(currentSyncContext);
   2109                                 }
   2110                             } catch (RemoteException e) {
   2111                                 // We don't need to retry this in this case.
   2112                             }
   2113 
   2114                             // pretend that the sync failed with an IOException,
   2115                             // which is a soft error
   2116                             SyncResult syncResult = new SyncResult();
   2117                             syncResult.stats.numIoExceptions++;
   2118                             runSyncFinishedOrCanceledLocked(syncResult, currentSyncContext);
   2119 
   2120                             // since a sync just finished check if it is time to start a new sync
   2121                             nextPendingSyncTime = maybeStartNextSyncLocked();
   2122                         }
   2123 
   2124                         break;
   2125                     }
   2126 
   2127                     case SyncHandler.MESSAGE_SYNC_ALARM: {
   2128                         boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
   2129                         if (isLoggable) {
   2130                             Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_SYNC_ALARM");
   2131                         }
   2132                         mAlarmScheduleTime = null;
   2133                         try {
   2134                             nextPendingSyncTime = maybeStartNextSyncLocked();
   2135                         } finally {
   2136                             mHandleAlarmWakeLock.release();
   2137                         }
   2138                         break;
   2139                     }
   2140 
   2141                     case SyncHandler.MESSAGE_CHECK_ALARMS:
   2142                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
   2143                             Log.v(TAG, "handleSyncHandlerMessage: MESSAGE_CHECK_ALARMS");
   2144                         }
   2145                         nextPendingSyncTime = maybeStartNextSyncLocked();
   2146                         break;
   2147                 }
   2148             } finally {
   2149                 manageSyncNotificationLocked();
   2150                 manageSyncAlarmLocked(earliestFuturePollTime, nextPendingSyncTime);
   2151                 mSyncTimeTracker.update();
   2152                 mSyncManagerWakeLock.release();
   2153             }
   2154         }
   2155 
   2156         private boolean isDispatchable(SyncStorageEngine.EndPoint target) {
   2157             final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
   2158             if (target.target_provider) {
   2159                 // skip the sync if the account of this operation no longer exists
   2160                 AccountAndUser[] accounts = mRunningAccounts;
   2161                 if (!containsAccountAndUser(
   2162                         accounts, target.account, target.userId)) {
   2163                     return false;
   2164                 }
   2165                 if (!mSyncStorageEngine.getMasterSyncAutomatically(target.userId)
   2166                         || !mSyncStorageEngine.getSyncAutomatically(
   2167                                 target.account,
   2168                                 target.userId,
   2169                                 target.provider)) {
   2170                     if (isLoggable) {
   2171                         Log.v(TAG, "    Not scheduling periodic operation: sync turned off.");
   2172                     }
   2173                     return false;
   2174                 }
   2175                 if (getIsSyncable(target.account, target.userId, target.provider)
   2176                         == 0) {
   2177                     if (isLoggable) {
   2178                         Log.v(TAG, "    Not scheduling periodic operation: isSyncable == 0.");
   2179                     }
   2180                     return false;
   2181                 }
   2182             } else if (target.target_service) {
   2183                 if (mSyncStorageEngine.getIsTargetServiceActive(target.service, target.userId)) {
   2184                     if (isLoggable) {
   2185                         Log.v(TAG, "   Not scheduling periodic operation: isEnabled == 0.");
   2186                     }
   2187                     return false;
   2188                 }
   2189             }
   2190             return true;
   2191         }
   2192 
   2193         /**
   2194          * Turn any periodic sync operations that are ready to run into pending sync operations.
   2195          * @return the desired start time of the earliest future periodic sync operation,
   2196          * in milliseconds since boot
   2197          */
   2198         private long scheduleReadyPeriodicSyncs() {
   2199             final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
   2200             if (isLoggable) {
   2201                 Log.v(TAG, "scheduleReadyPeriodicSyncs");
   2202             }
   2203             long earliestFuturePollTime = Long.MAX_VALUE;
   2204 
   2205             final long nowAbsolute = System.currentTimeMillis();
   2206             final long shiftedNowAbsolute = (0 < nowAbsolute - mSyncRandomOffsetMillis)
   2207                     ? (nowAbsolute - mSyncRandomOffsetMillis) : 0;
   2208 
   2209             ArrayList<Pair<AuthorityInfo, SyncStatusInfo>> infos = mSyncStorageEngine
   2210                     .getCopyOfAllAuthoritiesWithSyncStatus();
   2211             for (Pair<AuthorityInfo, SyncStatusInfo> info : infos) {
   2212                 final AuthorityInfo authorityInfo = info.first;
   2213                 final SyncStatusInfo status = info.second;
   2214 
   2215                 if (TextUtils.isEmpty(authorityInfo.target.provider)) {
   2216                     Log.e(TAG, "Got an empty provider string. Skipping: "
   2217                         + authorityInfo.target.provider);
   2218                     continue;
   2219                 }
   2220 
   2221                 if (!isDispatchable(authorityInfo.target)) {
   2222                     continue;
   2223                 }
   2224 
   2225                 for (int i = 0, N = authorityInfo.periodicSyncs.size(); i < N; i++) {
   2226                     final PeriodicSync sync = authorityInfo.periodicSyncs.get(i);
   2227                     final Bundle extras = sync.extras;
   2228                     final Long periodInMillis = sync.period * 1000;
   2229                     final Long flexInMillis = sync.flexTime * 1000;
   2230                     // Skip if the period is invalid.
   2231                     if (periodInMillis <= 0) {
   2232                         continue;
   2233                     }
   2234                     // Find when this periodic sync was last scheduled to run.
   2235                     final long lastPollTimeAbsolute = status.getPeriodicSyncTime(i);
   2236                     final long shiftedLastPollTimeAbsolute =
   2237                             (0 < lastPollTimeAbsolute - mSyncRandomOffsetMillis) ?
   2238                                     (lastPollTimeAbsolute - mSyncRandomOffsetMillis) : 0;
   2239                     long remainingMillis
   2240                         = periodInMillis - (shiftedNowAbsolute % periodInMillis);
   2241                     long timeSinceLastRunMillis
   2242                         = (nowAbsolute - lastPollTimeAbsolute);
   2243                     // Schedule this periodic sync to run early if it's close enough to its next
   2244                     // runtime, and far enough from its last run time.
   2245                     // If we are early, there will still be time remaining in this period.
   2246                     boolean runEarly = remainingMillis <= flexInMillis
   2247                             && timeSinceLastRunMillis > periodInMillis - flexInMillis;
   2248                     if (isLoggable) {
   2249                         Log.v(TAG, "sync: " + i + " for " + authorityInfo.target + "."
   2250                         + " period: " + (periodInMillis)
   2251                         + " flex: " + (flexInMillis)
   2252                         + " remaining: " + (remainingMillis)
   2253                         + " time_since_last: " + timeSinceLastRunMillis
   2254                         + " last poll absol: " + lastPollTimeAbsolute
   2255                         + " last poll shifed: " + shiftedLastPollTimeAbsolute
   2256                         + " shifted now: " + shiftedNowAbsolute
   2257                         + " run_early: " + runEarly);
   2258                     }
   2259                     /*
   2260                      * Sync scheduling strategy: Set the next periodic sync
   2261                      * based on a random offset (in seconds). Also sync right
   2262                      * now if any of the following cases hold and mark it as
   2263                      * having been scheduled
   2264                      * Case 1: This sync is ready to run now.
   2265                      * Case 2: If the lastPollTimeAbsolute is in the
   2266                      * future, sync now and reinitialize. This can happen for
   2267                      * example if the user changed the time, synced and changed
   2268                      * back.
   2269                      * Case 3: If we failed to sync at the last scheduled time.
   2270                      * Case 4: This sync is close enough to the time that we can schedule it.
   2271                      */
   2272                     if (remainingMillis == periodInMillis // Case 1
   2273                             || lastPollTimeAbsolute > nowAbsolute // Case 2
   2274                             || timeSinceLastRunMillis >= periodInMillis // Case 3
   2275                             || runEarly) { // Case 4
   2276                         // Sync now
   2277                         SyncStorageEngine.EndPoint target = authorityInfo.target;
   2278                         final Pair<Long, Long> backoff =
   2279                                 mSyncStorageEngine.getBackoff(target);
   2280                         mSyncStorageEngine.setPeriodicSyncTime(authorityInfo.ident,
   2281                                 authorityInfo.periodicSyncs.get(i), nowAbsolute);
   2282 
   2283                         if (target.target_provider) {
   2284                             final RegisteredServicesCache.ServiceInfo<SyncAdapterType>
   2285                                 syncAdapterInfo = mSyncAdapters.getServiceInfo(
   2286                                     SyncAdapterType.newKey(
   2287                                             target.provider, target.account.type),
   2288                                     target.userId);
   2289                             if (syncAdapterInfo == null) {
   2290                                 continue;
   2291                             }
   2292                             scheduleSyncOperation(
   2293                                     new SyncOperation(target.account, target.userId,
   2294                                             SyncOperation.REASON_PERIODIC,
   2295                                             SyncStorageEngine.SOURCE_PERIODIC,
   2296                                             target.provider, extras,
   2297                                             0 /* runtime */, 0 /* flex */,
   2298                                             backoff != null ? backoff.first : 0,
   2299                                             mSyncStorageEngine.getDelayUntilTime(target),
   2300                                             syncAdapterInfo.type.allowParallelSyncs()));
   2301                         } else if (target.target_service) {
   2302                             scheduleSyncOperation(
   2303                                     new SyncOperation(target.service, target.userId,
   2304                                             SyncOperation.REASON_PERIODIC,
   2305                                             SyncStorageEngine.SOURCE_PERIODIC,
   2306                                             extras,
   2307                                             0 /* runtime */,
   2308                                             0 /* flex */,
   2309                                             backoff != null ? backoff.first : 0,
   2310                                             mSyncStorageEngine.getDelayUntilTime(target)));
   2311                         }
   2312                     }
   2313                     // Compute when this periodic sync should next run.
   2314                     long nextPollTimeAbsolute;
   2315                     if (runEarly) {
   2316                         // Add the time remaining so we don't get out of phase.
   2317                         nextPollTimeAbsolute = nowAbsolute + periodInMillis + remainingMillis;
   2318                     } else {
   2319                         nextPollTimeAbsolute = nowAbsolute + remainingMillis;
   2320                     }
   2321                     if (nextPollTimeAbsolute < earliestFuturePollTime) {
   2322                         earliestFuturePollTime = nextPollTimeAbsolute;
   2323                     }
   2324                 }
   2325             }
   2326 
   2327             if (earliestFuturePollTime == Long.MAX_VALUE) {
   2328                 return Long.MAX_VALUE;
   2329             }
   2330 
   2331             // convert absolute time to elapsed time
   2332             return SystemClock.elapsedRealtime() +
   2333                 ((earliestFuturePollTime < nowAbsolute) ?
   2334                     0 : (earliestFuturePollTime - nowAbsolute));
   2335         }
   2336 
   2337         private long maybeStartNextSyncLocked() {
   2338             final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
   2339             if (isLoggable) Log.v(TAG, "maybeStartNextSync");
   2340 
   2341             // If we aren't ready to run (e.g. the data connection is down), get out.
   2342             if (!mDataConnectionIsConnected) {
   2343                 if (isLoggable) {
   2344                     Log.v(TAG, "maybeStartNextSync: no data connection, skipping");
   2345                 }
   2346                 return Long.MAX_VALUE;
   2347             }
   2348 
   2349             if (mStorageIsLow) {
   2350                 if (isLoggable) {
   2351                     Log.v(TAG, "maybeStartNextSync: memory low, skipping");
   2352                 }
   2353                 return Long.MAX_VALUE;
   2354             }
   2355 
   2356             // If the accounts aren't known yet then we aren't ready to run. We will be kicked
   2357             // when the account lookup request does complete.
   2358             if (mRunningAccounts == INITIAL_ACCOUNTS_ARRAY) {
   2359                 if (isLoggable) {
   2360                     Log.v(TAG, "maybeStartNextSync: accounts not known, skipping");
   2361                 }
   2362                 return Long.MAX_VALUE;
   2363             }
   2364 
   2365             // Otherwise consume SyncOperations from the head of the SyncQueue until one is
   2366             // found that is runnable (not disabled, etc). If that one is ready to run then
   2367             // start it, otherwise just get out.
   2368             final long now = SystemClock.elapsedRealtime();
   2369 
   2370             // will be set to the next time that a sync should be considered for running
   2371             long nextReadyToRunTime = Long.MAX_VALUE;
   2372 
   2373             // order the sync queue, dropping syncs that are not allowed
   2374             ArrayList<SyncOperation> operations = new ArrayList<SyncOperation>();
   2375             synchronized (mSyncQueue) {
   2376                 if (isLoggable) {
   2377                     Log.v(TAG, "build the operation array, syncQueue size is "
   2378                         + mSyncQueue.getOperations().size());
   2379                 }
   2380                 final Iterator<SyncOperation> operationIterator =
   2381                         mSyncQueue.getOperations().iterator();
   2382 
   2383                 final ActivityManager activityManager
   2384                         = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
   2385                 final Set<Integer> removedUsers = Sets.newHashSet();
   2386                 while (operationIterator.hasNext()) {
   2387                     final SyncOperation op = operationIterator.next();
   2388 
   2389                     // If the user is not running, skip the request.
   2390                     if (!activityManager.isUserRunning(op.target.userId)) {
   2391                         final UserInfo userInfo = mUserManager.getUserInfo(op.target.userId);
   2392                         if (userInfo == null) {
   2393                             removedUsers.add(op.target.userId);
   2394                         }
   2395                         if (isLoggable) {
   2396                             Log.v(TAG, "    Dropping all sync operations for + "
   2397                                     + op.target.userId + ": user not running.");
   2398                         }
   2399                         continue;
   2400                     }
   2401                     if (!isOperationValidLocked(op)) {
   2402                         operationIterator.remove();
   2403                         mSyncStorageEngine.deleteFromPending(op.pendingOperation);
   2404                         continue;
   2405                     }
   2406                     // If the next run time is in the future, even given the flexible scheduling,
   2407                     // return the time.
   2408                     if (op.effectiveRunTime - op.flexTime > now) {
   2409                         if (nextReadyToRunTime > op.effectiveRunTime) {
   2410                             nextReadyToRunTime = op.effectiveRunTime;
   2411                         }
   2412                         if (isLoggable) {
   2413                             Log.v(TAG, "    Not running sync operation: Sync too far in future."
   2414                                     + "effective: " + op.effectiveRunTime + " flex: " + op.flexTime
   2415                                     + " now: " + now);
   2416                         }
   2417                         continue;
   2418                     }
   2419                     // Add this sync to be run.
   2420                     operations.add(op);
   2421                 }
   2422 
   2423                 for (Integer user : removedUsers) {
   2424                     // if it's still removed
   2425                     if (mUserManager.getUserInfo(user) == null) {
   2426                         onUserRemoved(user);
   2427                     }
   2428                 }
   2429             }
   2430 
   2431             // find the next operation to dispatch, if one is ready
   2432             // iterate from the top, keep issuing (while potentially canceling existing syncs)
   2433             // until the quotas are filled.
   2434             // once the quotas are filled iterate once more to find when the next one would be
   2435             // (also considering pre-emption reasons).
   2436             if (isLoggable) Log.v(TAG, "sort the candidate operations, size " + operations.size());
   2437             Collections.sort(operations);
   2438             if (isLoggable) Log.v(TAG, "dispatch all ready sync operations");
   2439             for (int i = 0, N = operations.size(); i < N; i++) {
   2440                 final SyncOperation candidate = operations.get(i);
   2441                 final boolean candidateIsInitialization = candidate.isInitialization();
   2442 
   2443                 int numInit = 0;
   2444                 int numRegular = 0;
   2445                 ActiveSyncContext conflict = null;
   2446                 ActiveSyncContext longRunning = null;
   2447                 ActiveSyncContext toReschedule = null;
   2448                 ActiveSyncContext oldestNonExpeditedRegular = null;
   2449 
   2450                 for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
   2451                     final SyncOperation activeOp = activeSyncContext.mSyncOperation;
   2452                     if (activeOp.isInitialization()) {
   2453                         numInit++;
   2454                     } else {
   2455                         numRegular++;
   2456                         if (!activeOp.isExpedited()) {
   2457                             if (oldestNonExpeditedRegular == null
   2458                                 || (oldestNonExpeditedRegular.mStartTime
   2459                                     > activeSyncContext.mStartTime)) {
   2460                                 oldestNonExpeditedRegular = activeSyncContext;
   2461                             }
   2462                         }
   2463                     }
   2464                     if (activeOp.isConflict(candidate)) {
   2465                         conflict = activeSyncContext;
   2466                         // don't break out since we want to do a full count of the varieties.
   2467                     } else {
   2468                         if (candidateIsInitialization == activeOp.isInitialization()
   2469                                 && activeSyncContext.mStartTime + MAX_TIME_PER_SYNC < now) {
   2470                             longRunning = activeSyncContext;
   2471                             // don't break out since we want to do a full count of the varieties
   2472                         }
   2473                     }
   2474                 }
   2475 
   2476                 if (isLoggable) {
   2477                     Log.v(TAG, "candidate " + (i + 1) + " of " + N + ": " + candidate);
   2478                     Log.v(TAG, "  numActiveInit=" + numInit + ", numActiveRegular=" + numRegular);
   2479                     Log.v(TAG, "  longRunning: " + longRunning);
   2480                     Log.v(TAG, "  conflict: " + conflict);
   2481                     Log.v(TAG, "  oldestNonExpeditedRegular: " + oldestNonExpeditedRegular);
   2482                 }
   2483 
   2484                 final boolean roomAvailable = candidateIsInitialization
   2485                         ? numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS
   2486                         : numRegular < MAX_SIMULTANEOUS_REGULAR_SYNCS;
   2487 
   2488                 if (conflict != null) {
   2489                     if (candidateIsInitialization && !conflict.mSyncOperation.isInitialization()
   2490                             && numInit < MAX_SIMULTANEOUS_INITIALIZATION_SYNCS) {
   2491                         toReschedule = conflict;
   2492                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
   2493                             Log.v(TAG, "canceling and rescheduling sync since an initialization "
   2494                                     + "takes higher priority, " + conflict);
   2495                         }
   2496                     } else if (candidate.isExpedited() && !conflict.mSyncOperation.isExpedited()
   2497                             && (candidateIsInitialization
   2498                                 == conflict.mSyncOperation.isInitialization())) {
   2499                         toReschedule = conflict;
   2500                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
   2501                             Log.v(TAG, "canceling and rescheduling sync since an expedited "
   2502                                     + "takes higher priority, " + conflict);
   2503                         }
   2504                     } else {
   2505                         continue;
   2506                     }
   2507                 } else if (roomAvailable) {
   2508                     // dispatch candidate
   2509                 } else if (candidate.isExpedited() && oldestNonExpeditedRegular != null
   2510                            && !candidateIsInitialization) {
   2511                     // We found an active, non-expedited regular sync. We also know that the
   2512                     // candidate doesn't conflict with this active sync since conflict
   2513                     // is null. Reschedule the active sync and start the candidate.
   2514                     toReschedule = oldestNonExpeditedRegular;
   2515                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
   2516                         Log.v(TAG, "canceling and rescheduling sync since an expedited is ready to"
   2517                                 + " run, " + oldestNonExpeditedRegular);
   2518                     }
   2519                 } else if (longRunning != null
   2520                         && (candidateIsInitialization
   2521                             == longRunning.mSyncOperation.isInitialization())) {
   2522                     // We found an active, long-running sync. Reschedule the active
   2523                     // sync and start the candidate.
   2524                     toReschedule = longRunning;
   2525                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
   2526                         Log.v(TAG, "canceling and rescheduling sync since it ran roo long, "
   2527                               + longRunning);
   2528                     }
   2529                 } else {
   2530                     // we were unable to find or make space to run this candidate, go on to
   2531                     // the next one
   2532                     continue;
   2533                 }
   2534 
   2535                 if (toReschedule != null) {
   2536                     runSyncFinishedOrCanceledLocked(null, toReschedule);
   2537                     scheduleSyncOperation(toReschedule.mSyncOperation);
   2538                 }
   2539                 synchronized (mSyncQueue) {
   2540                     mSyncQueue.remove(candidate);
   2541                 }
   2542                 dispatchSyncOperation(candidate);
   2543             }
   2544 
   2545             return nextReadyToRunTime;
   2546         }
   2547 
   2548         /**
   2549          * Determine if a sync is no longer valid and should be dropped from the sync queue and its
   2550          * pending op deleted.
   2551          * @param op operation for which the sync is to be scheduled.
   2552          */
   2553         private boolean isOperationValidLocked(SyncOperation op) {
   2554             final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
   2555             int targetUid;
   2556             int state;
   2557             final SyncStorageEngine.EndPoint target = op.target;
   2558             boolean syncEnabled = mSyncStorageEngine.getMasterSyncAutomatically(target.userId);
   2559             if (target.target_provider) {
   2560                 // Drop the sync if the account of this operation no longer exists.
   2561                 AccountAndUser[] accounts = mRunningAccounts;
   2562                 if (!containsAccountAndUser(accounts, target.account, target.userId)) {
   2563                     if (isLoggable) {
   2564                         Log.v(TAG, "    Dropping sync operation: account doesn't exist.");
   2565                     }
   2566                     return false;
   2567                 }
   2568                 // Drop this sync request if it isn't syncable.
   2569                 state = getIsSyncable(target.account, target.userId, target.provider);
   2570                 if (state == 0) {
   2571                     if (isLoggable) {
   2572                         Log.v(TAG, "    Dropping sync operation: isSyncable == 0.");
   2573                     }
   2574                     return false;
   2575                 }
   2576                 syncEnabled = syncEnabled && mSyncStorageEngine.getSyncAutomatically(
   2577                         target.account, target.userId, target.provider);
   2578 
   2579                 final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
   2580                 syncAdapterInfo = mSyncAdapters.getServiceInfo(
   2581                         SyncAdapterType.newKey(
   2582                                 target.provider, target.account.type), target.userId);
   2583                 if (syncAdapterInfo != null) {
   2584                     targetUid = syncAdapterInfo.uid;
   2585                 } else {
   2586                     if (isLoggable) {
   2587                         Log.v(TAG, "    Dropping sync operation: No sync adapter registered"
   2588                                 + "for: " + target);
   2589                     }
   2590                     return false;
   2591                 }
   2592             } else if (target.target_service) {
   2593                 state = mSyncStorageEngine.getIsTargetServiceActive(target.service, target.userId)
   2594                             ? 1 : 0;
   2595                 if (state == 0) {
   2596                     // TODO: Change this to not drop disabled syncs - keep them in the pending queue.
   2597                     if (isLoggable) {
   2598                         Log.v(TAG, "    Dropping sync operation: isActive == 0.");
   2599                     }
   2600                     return false;
   2601                 }
   2602                 try {
   2603                     targetUid = mContext.getPackageManager()
   2604                             .getServiceInfo(target.service, 0)
   2605                             .applicationInfo
   2606                             .uid;
   2607                 } catch (PackageManager.NameNotFoundException e) {
   2608                     if (isLoggable) {
   2609                         Log.v(TAG, "    Dropping sync operation: No service registered for: "
   2610                                 + target.service);
   2611                     }
   2612                     return false;
   2613                 }
   2614             } else {
   2615                 Log.e(TAG, "Unknown target for Sync Op: " + target);
   2616                 return false;
   2617             }
   2618 
   2619             // We ignore system settings that specify the sync is invalid if:
   2620             // 1) It's manual - we try it anyway. When/if it fails it will be rescheduled.
   2621             //      or
   2622             // 2) it's an initialisation sync - we just need to connect to it.
   2623             final boolean ignoreSystemConfiguration =
   2624                     op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS, false)
   2625                     || (state < 0);
   2626 
   2627             // Sync not enabled.
   2628             if (!syncEnabled && !ignoreSystemConfiguration) {
   2629                 if (isLoggable) {
   2630                     Log.v(TAG, "    Dropping sync operation: disallowed by settings/network.");
   2631                 }
   2632                 return false;
   2633             }
   2634             // Network down.
   2635             final NetworkInfo networkInfo = getConnectivityManager()
   2636                     .getActiveNetworkInfoForUid(targetUid);
   2637             final boolean uidNetworkConnected = networkInfo != null && networkInfo.isConnected();
   2638             if (!uidNetworkConnected && !ignoreSystemConfiguration) {
   2639                 if (isLoggable) {
   2640                     Log.v(TAG, "    Dropping sync operation: disallowed by settings/network.");
   2641                 }
   2642                 return false;
   2643             }
   2644             // Metered network.
   2645             if (op.isNotAllowedOnMetered() && getConnectivityManager().isActiveNetworkMetered()
   2646                     && !ignoreSystemConfiguration) {
   2647                 if (isLoggable) {
   2648                     Log.v(TAG, "    Dropping sync operation: not allowed on metered network.");
   2649                 }
   2650                 return false;
   2651             }
   2652             return true;
   2653         }
   2654 
   2655         private boolean dispatchSyncOperation(SyncOperation op) {
   2656             if (Log.isLoggable(TAG, Log.VERBOSE)) {
   2657                 Log.v(TAG, "dispatchSyncOperation: we are going to sync " + op);
   2658                 Log.v(TAG, "num active syncs: " + mActiveSyncContexts.size());
   2659                 for (ActiveSyncContext syncContext : mActiveSyncContexts) {
   2660                     Log.v(TAG, syncContext.toString());
   2661                 }
   2662             }
   2663             // Connect to the sync adapter.
   2664             int targetUid;
   2665             ComponentName targetComponent;
   2666             final SyncStorageEngine.EndPoint info = op.target;
   2667             if (info.target_provider) {
   2668                 SyncAdapterType syncAdapterType =
   2669                         SyncAdapterType.newKey(info.provider, info.account.type);
   2670                 final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo;
   2671                 syncAdapterInfo = mSyncAdapters.getServiceInfo(syncAdapterType, info.userId);
   2672                 if (syncAdapterInfo == null) {
   2673                     Log.d(TAG, "can't find a sync adapter for " + syncAdapterType
   2674                             + ", removing settings for it");
   2675                     mSyncStorageEngine.removeAuthority(info);
   2676                     return false;
   2677                 }
   2678                 targetUid = syncAdapterInfo.uid;
   2679                 targetComponent = syncAdapterInfo.componentName;
   2680             } else {
   2681                 // TODO: Store the uid of the service as part of the authority info in order to
   2682                 // avoid this call?
   2683                 try {
   2684                     targetUid = mContext.getPackageManager()
   2685                             .getServiceInfo(info.service, 0)
   2686                             .applicationInfo
   2687                             .uid;
   2688                     targetComponent = info.service;
   2689                 } catch(PackageManager.NameNotFoundException e) {
   2690                     Log.d(TAG, "Can't find a service for " + info.service
   2691                             + ", removing settings for it");
   2692                     mSyncStorageEngine.removeAuthority(info);
   2693                     return false;
   2694                 }
   2695             }
   2696             ActiveSyncContext activeSyncContext =
   2697                     new ActiveSyncContext(op, insertStartSyncEvent(op), targetUid);
   2698             activeSyncContext.mSyncInfo = mSyncStorageEngine.addActiveSync(activeSyncContext);
   2699             mActiveSyncContexts.add(activeSyncContext);
   2700             if (!activeSyncContext.mSyncOperation.isInitialization() &&
   2701                     !activeSyncContext.mSyncOperation.isExpedited() &&
   2702                     !activeSyncContext.mSyncOperation.isManual() &&
   2703                     !activeSyncContext.mSyncOperation.isIgnoreSettings()) {
   2704                 // Post message to expire this sync if it runs for too long.
   2705                 postSyncExpiryMessage(activeSyncContext);
   2706             }
   2707             if (Log.isLoggable(TAG, Log.VERBOSE)) {
   2708                 Log.v(TAG, "dispatchSyncOperation: starting " + activeSyncContext);
   2709             }
   2710             if (!activeSyncContext.bindToSyncAdapter(targetComponent, info.userId)) {
   2711                 Log.e(TAG, "Bind attempt failed - target: " + targetComponent);
   2712                 closeActiveSyncContext(activeSyncContext);
   2713                 return false;
   2714             }
   2715 
   2716             return true;
   2717         }
   2718 
   2719         private void runBoundToAdapter(final ActiveSyncContext activeSyncContext,
   2720                 IBinder syncAdapter) {
   2721             final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
   2722             try {
   2723                 activeSyncContext.mIsLinkedToDeath = true;
   2724                 syncAdapter.linkToDeath(activeSyncContext, 0);
   2725 
   2726                 if (syncOperation.target.target_provider) {
   2727                     activeSyncContext.mSyncAdapter = ISyncAdapter.Stub.asInterface(syncAdapter);
   2728                     activeSyncContext.mSyncAdapter
   2729                         .startSync(activeSyncContext, syncOperation.target.provider,
   2730                                 syncOperation.target.account, syncOperation.extras);
   2731                 } else if (syncOperation.target.target_service) {
   2732                     activeSyncContext.mSyncServiceAdapter =
   2733                             ISyncServiceAdapter.Stub.asInterface(syncAdapter);
   2734                     activeSyncContext.mSyncServiceAdapter
   2735                         .startSync(activeSyncContext, syncOperation.extras);
   2736                 }
   2737             } catch (RemoteException remoteExc) {
   2738                 Log.d(TAG, "maybeStartNextSync: caught a RemoteException, rescheduling", remoteExc);
   2739                 closeActiveSyncContext(activeSyncContext);
   2740                 increaseBackoffSetting(syncOperation);
   2741                 scheduleSyncOperation(
   2742                         new SyncOperation(syncOperation, 0L /* newRunTimeFromNow */));
   2743             } catch (RuntimeException exc) {
   2744                 closeActiveSyncContext(activeSyncContext);
   2745                 Log.e(TAG, "Caught RuntimeException while starting the sync " + syncOperation, exc);
   2746             }
   2747         }
   2748 
   2749         /**
   2750          * Cancel the sync for the provided target that matches the given bundle.
   2751          * @param info can have null fields to indicate all the active syncs for that field.
   2752          */
   2753         private void cancelActiveSyncLocked(SyncStorageEngine.EndPoint info, Bundle extras) {
   2754             ArrayList<ActiveSyncContext> activeSyncs =
   2755                     new ArrayList<ActiveSyncContext>(mActiveSyncContexts);
   2756             for (ActiveSyncContext activeSyncContext : activeSyncs) {
   2757                 if (activeSyncContext != null) {
   2758                     final SyncStorageEngine.EndPoint opInfo =
   2759                             activeSyncContext.mSyncOperation.target;
   2760                     if (!opInfo.matchesSpec(info)) {
   2761                         continue;
   2762                     }
   2763                     if (extras != null &&
   2764                             !syncExtrasEquals(activeSyncContext.mSyncOperation.extras,
   2765                                     extras,
   2766                                     false /* no config settings */)) {
   2767                         continue;
   2768                     }
   2769                     runSyncFinishedOrCanceledLocked(null /* no result since this is a cancel */,
   2770                             activeSyncContext);
   2771                 }
   2772             }
   2773         }
   2774 
   2775         private void runSyncFinishedOrCanceledLocked(SyncResult syncResult,
   2776                 ActiveSyncContext activeSyncContext) {
   2777             boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
   2778 
   2779             final SyncOperation syncOperation = activeSyncContext.mSyncOperation;
   2780             final SyncStorageEngine.EndPoint info = syncOperation.target;
   2781 
   2782             if (activeSyncContext.mIsLinkedToDeath) {
   2783                 if (info.target_provider) {
   2784                     activeSyncContext.mSyncAdapter.asBinder().unlinkToDeath(activeSyncContext, 0);
   2785                 } else {
   2786                     activeSyncContext.mSyncServiceAdapter.asBinder()
   2787                         .unlinkToDeath(activeSyncContext, 0);
   2788                 }
   2789                 activeSyncContext.mIsLinkedToDeath = false;
   2790             }
   2791             closeActiveSyncContext(activeSyncContext);
   2792             final long elapsedTime = SystemClock.elapsedRealtime() - activeSyncContext.mStartTime;
   2793             String historyMessage;
   2794             int downstreamActivity;
   2795             int upstreamActivity;
   2796             if (syncResult != null) {
   2797                 if (isLoggable) {
   2798                     Log.v(TAG, "runSyncFinishedOrCanceled [finished]: "
   2799                             + syncOperation + ", result " + syncResult);
   2800                 }
   2801 
   2802                 if (!syncResult.hasError()) {
   2803                     historyMessage = SyncStorageEngine.MESG_SUCCESS;
   2804                     // TODO: set these correctly when the SyncResult is extended to include it
   2805                     downstreamActivity = 0;
   2806                     upstreamActivity = 0;
   2807                     clearBackoffSetting(syncOperation);
   2808                 } else {
   2809                     Log.d(TAG, "failed sync operation " + syncOperation + ", " + syncResult);
   2810                     // the operation failed so increase the backoff time
   2811                     increaseBackoffSetting(syncOperation);
   2812                     // reschedule the sync if so indicated by the syncResult
   2813                     maybeRescheduleSync(syncResult, syncOperation);
   2814                     historyMessage = ContentResolver.syncErrorToString(
   2815                             syncResultToErrorNumber(syncResult));
   2816                     // TODO: set these correctly when the SyncResult is extended to include it
   2817                     downstreamActivity = 0;
   2818                     upstreamActivity = 0;
   2819                 }
   2820                 setDelayUntilTime(syncOperation, syncResult.delayUntil);
   2821             } else {
   2822                 if (isLoggable) {
   2823                     Log.v(TAG, "runSyncFinishedOrCanceled [canceled]: " + syncOperation);
   2824                 }
   2825                 if (activeSyncContext.mSyncAdapter != null) {
   2826                     try {
   2827                         activeSyncContext.mSyncAdapter.cancelSync(activeSyncContext);
   2828                     } catch (RemoteException e) {
   2829                         // we don't need to retry this in this case
   2830                     }
   2831                 } else if (activeSyncContext.mSyncServiceAdapter != null) {
   2832                     try {
   2833                         activeSyncContext.mSyncServiceAdapter.cancelSync(activeSyncContext);
   2834                     } catch (RemoteException e) {
   2835                         // we don't need to retry this in this case
   2836                     }
   2837                 }
   2838                 historyMessage = SyncStorageEngine.MESG_CANCELED;
   2839                 downstreamActivity = 0;
   2840                 upstreamActivity = 0;
   2841             }
   2842 
   2843             stopSyncEvent(activeSyncContext.mHistoryRowId, syncOperation, historyMessage,
   2844                     upstreamActivity, downstreamActivity, elapsedTime);
   2845             // Check for full-resync and schedule it after closing off the last sync.
   2846             if (info.target_provider) {
   2847                 if (syncResult != null && syncResult.tooManyDeletions) {
   2848                     installHandleTooManyDeletesNotification(info.account,
   2849                             info.provider, syncResult.stats.numDeletes,
   2850                             info.userId);
   2851                 } else {
   2852                     mNotificationMgr.cancelAsUser(null,
   2853                             info.account.hashCode() ^ info.provider.hashCode(),
   2854                             new UserHandle(info.userId));
   2855                 }
   2856                 if (syncResult != null && syncResult.fullSyncRequested) {
   2857                     scheduleSyncOperation(
   2858                             new SyncOperation(info.account, info.userId,
   2859                                 syncOperation.reason,
   2860                                 syncOperation.syncSource, info.provider, new Bundle(),
   2861                                 0 /* delay */, 0 /* flex */,
   2862                                 syncOperation.backoff, syncOperation.delayUntil,
   2863                                 syncOperation.allowParallelSyncs));
   2864                 }
   2865             } else {
   2866                 if (syncResult != null && syncResult.fullSyncRequested) {
   2867                     scheduleSyncOperation(
   2868                             new SyncOperation(info.service, info.userId,
   2869                                 syncOperation.reason,
   2870                                 syncOperation.syncSource, new Bundle(),
   2871                                 0 /* delay */, 0 /* flex */,
   2872                                 syncOperation.backoff, syncOperation.delayUntil));
   2873                 }
   2874             }
   2875             // no need to schedule an alarm, as that will be done by our caller.
   2876         }
   2877 
   2878         private void closeActiveSyncContext(ActiveSyncContext activeSyncContext) {
   2879             activeSyncContext.close();
   2880             mActiveSyncContexts.remove(activeSyncContext);
   2881             mSyncStorageEngine.removeActiveSync(activeSyncContext.mSyncInfo,
   2882                     activeSyncContext.mSyncOperation.target.userId);
   2883             removeSyncExpiryMessage(activeSyncContext);
   2884         }
   2885 
   2886         /**
   2887          * Convert the error-containing SyncResult into the Sync.History error number. Since
   2888          * the SyncResult may indicate multiple errors at once, this method just returns the
   2889          * most "serious" error.
   2890          * @param syncResult the SyncResult from which to read
   2891          * @return the most "serious" error set in the SyncResult
   2892          * @throws IllegalStateException if the SyncResult does not indicate any errors.
   2893          *   If SyncResult.error() is true then it is safe to call this.
   2894          */
   2895         private int syncResultToErrorNumber(SyncResult syncResult) {
   2896             if (syncResult.syncAlreadyInProgress)
   2897                 return ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS;
   2898             if (syncResult.stats.numAuthExceptions > 0)
   2899                 return ContentResolver.SYNC_ERROR_AUTHENTICATION;
   2900             if (syncResult.stats.numIoExceptions > 0)
   2901                 return ContentResolver.SYNC_ERROR_IO;
   2902             if (syncResult.stats.numParseExceptions > 0)
   2903                 return ContentResolver.SYNC_ERROR_PARSE;
   2904             if (syncResult.stats.numConflictDetectedExceptions > 0)
   2905                 return ContentResolver.SYNC_ERROR_CONFLICT;
   2906             if (syncResult.tooManyDeletions)
   2907                 return ContentResolver.SYNC_ERROR_TOO_MANY_DELETIONS;
   2908             if (syncResult.tooManyRetries)
   2909                 return ContentResolver.SYNC_ERROR_TOO_MANY_RETRIES;
   2910             if (syncResult.databaseError)
   2911                 return ContentResolver.SYNC_ERROR_INTERNAL;
   2912             throw new IllegalStateException("we are not in an error state, " + syncResult);
   2913         }
   2914 
   2915         private void manageSyncNotificationLocked() {
   2916             boolean shouldCancel;
   2917             boolean shouldInstall;
   2918 
   2919             if (mActiveSyncContexts.isEmpty()) {
   2920                 mSyncNotificationInfo.startTime = null;
   2921 
   2922                 // we aren't syncing. if the notification is active then remember that we need
   2923                 // to cancel it and then clear out the info
   2924                 shouldCancel = mSyncNotificationInfo.isActive;
   2925                 shouldInstall = false;
   2926             } else {
   2927                 // we are syncing
   2928                 final long now = SystemClock.elapsedRealtime();
   2929                 if (mSyncNotificationInfo.startTime == null) {
   2930                     mSyncNotificationInfo.startTime = now;
   2931                 }
   2932 
   2933                 // there are three cases:
   2934                 // - the notification is up: do nothing
   2935                 // - the notification is not up but it isn't time yet: don't install
   2936                 // - the notification is not up and it is time: need to install
   2937 
   2938                 if (mSyncNotificationInfo.isActive) {
   2939                     shouldInstall = shouldCancel = false;
   2940                 } else {
   2941                     // it isn't currently up, so there is nothing to cancel
   2942                     shouldCancel = false;
   2943 
   2944                     final boolean timeToShowNotification =
   2945                             now > mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY;
   2946                     if (timeToShowNotification) {
   2947                         shouldInstall = true;
   2948                     } else {
   2949                         // show the notification immediately if this is a manual sync
   2950                         shouldInstall = false;
   2951                         for (ActiveSyncContext activeSyncContext : mActiveSyncContexts) {
   2952                             final boolean manualSync = activeSyncContext.mSyncOperation.extras
   2953                                     .getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
   2954                             if (manualSync) {
   2955                                 shouldInstall = true;
   2956                                 break;
   2957                             }
   2958                         }
   2959                     }
   2960                 }
   2961             }
   2962 
   2963             if (shouldCancel && !shouldInstall) {
   2964                 mNeedSyncActiveNotification = false;
   2965                 sendSyncStateIntent();
   2966                 mSyncNotificationInfo.isActive = false;
   2967             }
   2968 
   2969             if (shouldInstall) {
   2970                 mNeedSyncActiveNotification = true;
   2971                 sendSyncStateIntent();
   2972                 mSyncNotificationInfo.isActive = true;
   2973             }
   2974         }
   2975 
   2976         private void manageSyncAlarmLocked(long nextPeriodicEventElapsedTime,
   2977                 long nextPendingEventElapsedTime) {
   2978             // in each of these cases the sync loop will be kicked, which will cause this
   2979             // method to be called again
   2980             if (!mDataConnectionIsConnected) return;
   2981             if (mStorageIsLow) return;
   2982 
   2983             // When the status bar notification should be raised
   2984             final long notificationTime =
   2985                     (!mSyncHandler.mSyncNotificationInfo.isActive
   2986                             && mSyncHandler.mSyncNotificationInfo.startTime != null)
   2987                             ? mSyncHandler.mSyncNotificationInfo.startTime + SYNC_NOTIFICATION_DELAY
   2988                             : Long.MAX_VALUE;
   2989 
   2990             // When we should consider canceling an active sync
   2991             long earliestTimeoutTime = Long.MAX_VALUE;
   2992             for (ActiveSyncContext currentSyncContext : mActiveSyncContexts) {
   2993                 final long currentSyncTimeoutTime =
   2994                         currentSyncContext.mTimeoutStartTime + MAX_TIME_PER_SYNC;
   2995                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
   2996                     Log.v(TAG, "manageSyncAlarm: active sync, mTimeoutStartTime + MAX is "
   2997                             + currentSyncTimeoutTime);
   2998                 }
   2999                 if (earliestTimeoutTime > currentSyncTimeoutTime) {
   3000                     earliestTimeoutTime = currentSyncTimeoutTime;
   3001                 }
   3002             }
   3003 
   3004             if (Log.isLoggable(TAG, Log.VERBOSE)) {
   3005                 Log.v(TAG, "manageSyncAlarm: notificationTime is " + notificationTime);
   3006             }
   3007 
   3008             if (Log.isLoggable(TAG, Log.VERBOSE)) {
   3009                 Log.v(TAG, "manageSyncAlarm: earliestTimeoutTime is " + earliestTimeoutTime);
   3010             }
   3011 
   3012             if (Log.isLoggable(TAG, Log.VERBOSE)) {
   3013                 Log.v(TAG, "manageSyncAlarm: nextPeriodicEventElapsedTime is "
   3014                         + nextPeriodicEventElapsedTime);
   3015             }
   3016             if (Log.isLoggable(TAG, Log.VERBOSE)) {
   3017                 Log.v(TAG, "manageSyncAlarm: nextPendingEventElapsedTime is "
   3018                         + nextPendingEventElapsedTime);
   3019             }
   3020 
   3021             long alarmTime = Math.min(notificationTime, earliestTimeoutTime);
   3022             alarmTime = Math.min(alarmTime, nextPeriodicEventElapsedTime);
   3023             alarmTime = Math.min(alarmTime, nextPendingEventElapsedTime);
   3024 
   3025             // Bound the alarm time.
   3026             final long now = SystemClock.elapsedRealtime();
   3027             if (alarmTime < now + SYNC_ALARM_TIMEOUT_MIN) {
   3028                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
   3029                     Log.v(TAG, "manageSyncAlarm: the alarmTime is too small, "
   3030                             + alarmTime + ", setting to " + (now + SYNC_ALARM_TIMEOUT_MIN));
   3031                 }
   3032                 alarmTime = now + SYNC_ALARM_TIMEOUT_MIN;
   3033             } else if (alarmTime > now + SYNC_ALARM_TIMEOUT_MAX) {
   3034                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
   3035                     Log.v(TAG, "manageSyncAlarm: the alarmTime is too large, "
   3036                             + alarmTime + ", setting to " + (now + SYNC_ALARM_TIMEOUT_MIN));
   3037                 }
   3038                 alarmTime = now + SYNC_ALARM_TIMEOUT_MAX;
   3039             }
   3040 
   3041             // determine if we need to set or cancel the alarm
   3042             boolean shouldSet = false;
   3043             boolean shouldCancel = false;
   3044             final boolean alarmIsActive = (mAlarmScheduleTime != null) && (now < mAlarmScheduleTime);
   3045             final boolean needAlarm = alarmTime != Long.MAX_VALUE;
   3046             if (needAlarm) {
   3047                 // Need the alarm if
   3048                 //  - it's currently not set
   3049                 //  - if the alarm is set in the past.
   3050                 if (!alarmIsActive || alarmTime < mAlarmScheduleTime) {
   3051                     shouldSet = true;
   3052                 }
   3053             } else {
   3054                 shouldCancel = alarmIsActive;
   3055             }
   3056 
   3057             // Set or cancel the alarm as directed.
   3058             ensureAlarmService();
   3059             if (shouldSet) {
   3060                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
   3061                     Log.v(TAG, "requesting that the alarm manager wake us up at elapsed time "
   3062                             + alarmTime + ", now is " + now + ", " + ((alarmTime - now) / 1000)
   3063                             + " secs from now");
   3064                 }
   3065                 mAlarmScheduleTime = alarmTime;
   3066                 mAlarmService.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime,
   3067                         mSyncAlarmIntent);
   3068             } else if (shouldCancel) {
   3069                 mAlarmScheduleTime = null;
   3070                 mAlarmService.cancel(mSyncAlarmIntent);
   3071             }
   3072         }
   3073 
   3074         private void sendSyncStateIntent() {
   3075             Intent syncStateIntent = new Intent(Intent.ACTION_SYNC_STATE_CHANGED);
   3076             syncStateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
   3077             syncStateIntent.putExtra("active", mNeedSyncActiveNotification);
   3078             syncStateIntent.putExtra("failing", false);
   3079             mContext.sendBroadcastAsUser(syncStateIntent, UserHandle.OWNER);
   3080         }
   3081 
   3082         private void installHandleTooManyDeletesNotification(Account account, String authority,
   3083                 long numDeletes, int userId) {
   3084             if (mNotificationMgr == null) return;
   3085 
   3086             final ProviderInfo providerInfo = mContext.getPackageManager().resolveContentProvider(
   3087                     authority, 0 /* flags */);
   3088             if (providerInfo == null) {
   3089                 return;
   3090             }
   3091             CharSequence authorityName = providerInfo.loadLabel(mContext.getPackageManager());
   3092 
   3093             Intent clickIntent = new Intent(mContext, SyncActivityTooManyDeletes.class);
   3094             clickIntent.putExtra("account", account);
   3095             clickIntent.putExtra("authority", authority);
   3096             clickIntent.putExtra("provider", authorityName.toString());
   3097             clickIntent.putExtra("numDeletes", numDeletes);
   3098 
   3099             if (!isActivityAvailable(clickIntent)) {
   3100                 Log.w(TAG, "No activity found to handle too many deletes.");
   3101                 return;
   3102             }
   3103 
   3104             UserHandle user = new UserHandle(userId);
   3105             final PendingIntent pendingIntent = PendingIntent
   3106                     .getActivityAsUser(mContext, 0, clickIntent,
   3107                             PendingIntent.FLAG_CANCEL_CURRENT, null, user);
   3108 
   3109             CharSequence tooManyDeletesDescFormat = mContext.getResources().getText(
   3110                     R.string.contentServiceTooManyDeletesNotificationDesc);
   3111 
   3112             Context contextForUser = getContextForUser(user);
   3113             Notification notification =
   3114                 new Notification(R.drawable.stat_notify_sync_error,
   3115                         mContext.getString(R.string.contentServiceSync),
   3116                         System.currentTimeMillis());
   3117             notification.color = contextForUser.getResources().getColor(
   3118                     com.android.internal.R.color.system_notification_accent_color);
   3119             notification.setLatestEventInfo(contextForUser,
   3120                     contextForUser.getString(R.string.contentServiceSyncNotificationTitle),
   3121                     String.format(tooManyDeletesDescFormat.toString(), authorityName),
   3122                     pendingIntent);
   3123             notification.flags |= Notification.FLAG_ONGOING_EVENT;
   3124             mNotificationMgr.notifyAsUser(null, account.hashCode() ^ authority.hashCode(),
   3125                     notification, user);
   3126         }
   3127 
   3128         /**
   3129          * Checks whether an activity exists on the system image for the given intent.
   3130          *
   3131          * @param intent The intent for an activity.
   3132          * @return Whether or not an activity exists.
   3133          */
   3134         private boolean isActivityAvailable(Intent intent) {
   3135             PackageManager pm = mContext.getPackageManager();
   3136             List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
   3137             int listSize = list.size();
   3138             for (int i = 0; i < listSize; i++) {
   3139                 ResolveInfo resolveInfo = list.get(i);
   3140                 if ((resolveInfo.activityInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM)
   3141                         != 0) {
   3142                     return true;
   3143                 }
   3144             }
   3145 
   3146             return false;
   3147         }
   3148 
   3149         public long insertStartSyncEvent(SyncOperation syncOperation) {
   3150             final long now = System.currentTimeMillis();
   3151             EventLog.writeEvent(2720,
   3152                     syncOperation.toEventLog(SyncStorageEngine.EVENT_START));
   3153             return mSyncStorageEngine.insertStartSyncEvent(syncOperation, now);
   3154         }
   3155 
   3156         public void stopSyncEvent(long rowId, SyncOperation syncOperation, String resultMessage,
   3157                 int upstreamActivity, int downstreamActivity, long elapsedTime) {
   3158             EventLog.writeEvent(2720,
   3159                     syncOperation.toEventLog(SyncStorageEngine.EVENT_STOP));
   3160             mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime,
   3161                     resultMessage, downstreamActivity, upstreamActivity);
   3162         }
   3163     }
   3164 
   3165     private boolean isSyncStillActive(ActiveSyncContext activeSyncContext) {
   3166         for (ActiveSyncContext sync : mActiveSyncContexts) {
   3167             if (sync == activeSyncContext) {
   3168                 return true;
   3169             }
   3170         }
   3171         return false;
   3172     }
   3173 
   3174     /**
   3175      * Sync extra comparison function.
   3176      * @param b1 bundle to compare
   3177      * @param b2 other bundle to compare
   3178      * @param includeSyncSettings if false, ignore system settings in bundle.
   3179      */
   3180     public static boolean syncExtrasEquals(Bundle b1, Bundle b2, boolean includeSyncSettings) {
   3181         if (b1 == b2) {
   3182             return true;
   3183         }
   3184         // Exit early if we can.
   3185         if (includeSyncSettings && b1.size() != b2.size()) {
   3186             return false;
   3187         }
   3188         Bundle bigger = b1.size() > b2.size() ? b1 : b2;
   3189         Bundle smaller = b1.size() > b2.size() ? b2 : b1;
   3190         for (String key : bigger.keySet()) {
   3191             if (!includeSyncSettings && isSyncSetting(key)) {
   3192                 continue;
   3193             }
   3194             if (!smaller.containsKey(key)) {
   3195                 return false;
   3196             }
   3197             if (!bigger.get(key).equals(smaller.get(key))) {
   3198                 return false;
   3199             }
   3200         }
   3201         return true;
   3202     }
   3203 
   3204     /**
   3205      * TODO: Get rid of this when we separate sync settings extras from dev specified extras.
   3206      * @return true if the provided key is used by the SyncManager in scheduling the sync.
   3207      */
   3208     private static boolean isSyncSetting(String key) {
   3209         if (key.equals(ContentResolver.SYNC_EXTRAS_EXPEDITED)) {
   3210             return true;
   3211         }
   3212         if (key.equals(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS)) {
   3213             return true;
   3214         }
   3215         if (key.equals(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF)) {
   3216             return true;
   3217         }
   3218         if (key.equals(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY)) {
   3219             return true;
   3220         }
   3221         if (key.equals(ContentResolver.SYNC_EXTRAS_MANUAL)) {
   3222             return true;
   3223         }
   3224         if (key.equals(ContentResolver.SYNC_EXTRAS_UPLOAD)) {
   3225             return true;
   3226         }
   3227         if (key.equals(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS)) {
   3228             return true;
   3229         }
   3230         if (key.equals(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS)) {
   3231             return true;
   3232         }
   3233         if (key.equals(ContentResolver.SYNC_EXTRAS_EXPECTED_UPLOAD)) {
   3234             return true;
   3235         }
   3236         if (key.equals(ContentResolver.SYNC_EXTRAS_EXPECTED_DOWNLOAD)) {
   3237             return true;
   3238         }
   3239         if (key.equals(ContentResolver.SYNC_EXTRAS_PRIORITY)) {
   3240             return true;
   3241         }
   3242         if (key.equals(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED)) {
   3243             return true;
   3244         }
   3245         if (key.equals(ContentResolver.SYNC_EXTRAS_INITIALIZE)) {
   3246             return true;
   3247         }
   3248         return false;
   3249     }
   3250 
   3251     static class PrintTable {
   3252         private ArrayList<Object[]> mTable = Lists.newArrayList();
   3253         private final int mCols;
   3254 
   3255         PrintTable(int cols) {
   3256             mCols = cols;
   3257         }
   3258 
   3259         void set(int row, int col, Object... values) {
   3260             if (col + values.length > mCols) {
   3261                 throw new IndexOutOfBoundsException("Table only has " + mCols +
   3262                         " columns. can't set " + values.length + " at column " + col);
   3263             }
   3264             for (int i = mTable.size(); i <= row; i++) {
   3265                 final Object[] list = new Object[mCols];
   3266                 mTable.add(list);
   3267                 for (int j = 0; j < mCols; j++) {
   3268                     list[j] = "";
   3269                 }
   3270             }
   3271             System.arraycopy(values, 0, mTable.get(row), col, values.length);
   3272         }
   3273 
   3274         void writeTo(PrintWriter out) {
   3275             final String[] formats = new String[mCols];
   3276             int totalLength = 0;
   3277             for (int col = 0; col < mCols; ++col) {
   3278                 int maxLength = 0;
   3279                 for (Object[] row : mTable) {
   3280                     final int length = row[col].toString().length();
   3281                     if (length > maxLength) {
   3282                         maxLength = length;
   3283                     }
   3284                 }
   3285                 totalLength += maxLength;
   3286                 formats[col] = String.format("%%-%ds", maxLength);
   3287             }
   3288             formats[mCols - 1] = "%s";
   3289             printRow(out, formats, mTable.get(0));
   3290             totalLength += (mCols - 1) * 2;
   3291             for (int i = 0; i < totalLength; ++i) {
   3292                 out.print("-");
   3293             }
   3294             out.println();
   3295             for (int i = 1, mTableSize = mTable.size(); i < mTableSize; i++) {
   3296                 Object[] row = mTable.get(i);
   3297                 printRow(out, formats, row);
   3298             }
   3299         }
   3300 
   3301         private void printRow(PrintWriter out, String[] formats, Object[] row) {
   3302             for (int j = 0, rowLength = row.length; j < rowLength; j++) {
   3303                 out.printf(String.format(formats[j], row[j].toString()));
   3304                 out.print("  ");
   3305             }
   3306             out.println();
   3307         }
   3308 
   3309         public int getNumRows() {
   3310             return mTable.size();
   3311         }
   3312     }
   3313 
   3314     private Context getContextForUser(UserHandle user) {
   3315         try {
   3316             return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);
   3317         } catch (NameNotFoundException e) {
   3318             // Default to mContext, not finding the package system is running as is unlikely.
   3319             return mContext;
   3320         }
   3321     }
   3322 }
   3323