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