blob: 6d27bc78bbd2eb738b1b33f58398714b892f1241 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package android.content;
18
19import com.google.android.collect.Maps;
20
21import com.android.internal.R;
22import com.android.internal.util.ArrayUtils;
23
Fred Quintanad9d2f112009-04-23 13:36:27 -070024import android.accounts.Account;
25import android.accounts.AccountManager;
26import android.accounts.OnAccountsUpdatedListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import android.app.AlarmManager;
28import android.app.Notification;
29import android.app.NotificationManager;
30import android.app.PendingIntent;
31import android.content.pm.ApplicationInfo;
32import android.content.pm.IPackageManager;
33import android.content.pm.PackageManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034import android.content.pm.ResolveInfo;
Fred Quintana718d8a22009-04-29 17:53:20 -070035import android.content.pm.RegisteredServicesCache;
Fred Quintanac848b702009-08-25 20:18:46 -070036import android.content.pm.ProviderInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037import android.net.ConnectivityManager;
38import android.net.NetworkInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import android.os.Bundle;
40import android.os.Handler;
41import android.os.HandlerThread;
42import android.os.IBinder;
43import android.os.Looper;
44import android.os.Message;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080045import android.os.PowerManager;
46import android.os.Process;
47import android.os.RemoteException;
48import android.os.ServiceManager;
49import android.os.SystemClock;
50import android.os.SystemProperties;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051import android.provider.Settings;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052import android.text.format.DateUtils;
53import android.text.format.Time;
54import android.util.Config;
55import android.util.EventLog;
56import android.util.Log;
57
58import java.io.DataInputStream;
59import java.io.DataOutputStream;
60import java.io.File;
61import java.io.FileDescriptor;
62import java.io.FileInputStream;
63import java.io.FileNotFoundException;
64import java.io.FileOutputStream;
65import java.io.IOException;
66import java.io.PrintWriter;
67import java.util.ArrayList;
68import java.util.HashMap;
Dianne Hackborn231cc602009-04-27 17:10:36 -070069import java.util.HashSet;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080070import java.util.Iterator;
71import java.util.List;
72import java.util.Map;
73import java.util.PriorityQueue;
74import java.util.Random;
Fred Quintanaac9385e2009-06-22 18:00:59 -070075import java.util.Collection;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080076
77/**
78 * @hide
79 */
Fred Quintanad9d2f112009-04-23 13:36:27 -070080class SyncManager implements OnAccountsUpdatedListener {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080081 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 Project9066cfe2009-03-03 19:31:44 -0800121
122 private String mStatusText = "";
123 private long mHeartbeatTime = 0;
124
Fred Quintanad9d2f112009-04-23 13:36:27 -0700125 private volatile Account[] mAccounts = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800126
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 Project9066cfe2009-03-03 19:31:44 -0800131
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 Quintanaf892fb32009-08-27 21:32:08 -0700151 // Synchronized on "this". Instead of using this directly one should instead call
152 // its accessor, getConnManager().
153 private ConnectivityManager mConnManagerDoNotUseDirectly;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800154
Fred Quintana718d8a22009-04-29 17:53:20 -0700155 private final SyncAdaptersCache mSyncAdapters;
156
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800157 private BroadcastReceiver mStorageIntentReceiver =
158 new BroadcastReceiver() {
159 public void onReceive(Context context, Intent intent) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800160 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 Quintanaac9385e2009-06-22 18:00:59 -0700166 cancelActiveSync(null /* any account */, null /* any authority */);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800167 } 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 Quintana60307342009-03-24 22:48:12 -0700177 private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
178 public void onReceive(Context context, Intent intent) {
179 if (!mFactoryTest) {
Fred Quintana4f9cfc52009-09-02 15:20:23 -0700180 mBootCompleted = true;
Fred Quintanad9d2f112009-04-23 13:36:27 -0700181 AccountManager.get(mContext).addOnAccountsUpdatedListener(SyncManager.this,
182 mSyncHandler, true /* updateImmediately */);
Fred Quintana60307342009-03-24 22:48:12 -0700183 }
184 }
185 };
186
Fred Quintanad9d2f112009-04-23 13:36:27 -0700187 public void onAccountsUpdated(Account[] accounts) {
Fred Quintana4a6679b2009-08-17 13:05:39 -0700188 // remember if this was the first time this was called after an update
189 final boolean justBootedUp = mAccounts == null;
Fred Quintanad9d2f112009-04-23 13:36:27 -0700190 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 Quintana4a6679b2009-08-17 13:05:39 -0700209 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 Quintanad9d2f112009-04-23 13:36:27 -0700225 }
226 }
227
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800228 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 Hackborn55280a92009-05-07 15:53:46 -0700268 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 Project9066cfe2009-03-03 19:31:44 -0800276 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 Project9066cfe2009-03-03 19:31:44 -0800280 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 Quintana60307342009-03-24 22:48:12 -0700285 private final boolean mFactoryTest;
286
Fred Quintana4f9cfc52009-09-02 15:20:23 -0700287 private volatile boolean mBootCompleted = false;
288
Fred Quintanaf892fb32009-08-27 21:32:08 -0700289 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 Project9066cfe2009-03-03 19:31:44 -0800299 public SyncManager(Context context, boolean factoryTest) {
Fred Quintana60307342009-03-24 22:48:12 -0700300 mFactoryTest = factoryTest;
301
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800302 // 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 Quintana718d8a22009-04-29 17:53:20 -0700316 mSyncAdapters = new SyncAdaptersCache(mContext);
317
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800318 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 Quintana60307342009-03-24 22:48:12 -0700327 intentFilter = new IntentFilter(Intent.ACTION_BOOT_COMPLETED);
328 context.registerReceiver(mBootCompletedReceiver, intentFilter);
329
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800330 intentFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
331 intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
332 context.registerReceiver(mStorageIntentReceiver, intentFilter);
333
Dianne Hackborn55280a92009-05-07 15:53:46 -0700334 intentFilter = new IntentFilter(Intent.ACTION_SHUTDOWN);
335 intentFilter.setPriority(100);
336 context.registerReceiver(mShutdownIntentReceiver, intentFilter);
337
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800338 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 Hackborn231cc602009-04-27 17:10:36 -0700358 mSyncStorageEngine.addStatusChangeListener(
Fred Quintanaac9385e2009-06-22 18:00:59 -0700359 ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, new ISyncStatusObserver.Stub() {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700360 public void onStatusChanged(int which) {
361 // force the sync loop to run if the settings change
362 sendCheckAlarmsMessage();
363 }
364 });
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800365 }
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 Quintanaac9385e2009-06-22 18:00:59 -0700438 scheduleSync(null /* sync all syncable accounts */, null /* sync all syncable providers */,
Fred Quintana4a6679b2009-08-17 13:05:39 -0700439 new Bundle(), 0 /* no delay */, false /* onlyThoseWithUnkownSyncableState */);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800440 }
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 Hackborn231cc602009-04-27 17:10:36 -0700490 public SyncStorageEngine getSyncStorageEngine() {
491 return mSyncStorageEngine;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800492 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700493
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800494 private void ensureAlarmService() {
495 if (mAlarmService == null) {
496 mAlarmService = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE);
497 }
498 }
499
Fred Quintanad9d2f112009-04-23 13:36:27 -0700500 public Account getSyncingAccount() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800501 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 Quintanaac9385e2009-06-22 18:00:59 -0700535 * @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 Project9066cfe2009-03-03 19:31:44 -0800537 * @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 Quintana4a6679b2009-08-17 13:05:39 -0700541 * @param onlyThoseWithUnkownSyncableState
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800542 */
Fred Quintanaac9385e2009-06-22 18:00:59 -0700543 public void scheduleSync(Account requestedAccount, String requestedAuthority,
Fred Quintana4a6679b2009-08-17 13:05:39 -0700544 Bundle extras, long delay, boolean onlyThoseWithUnkownSyncableState) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800545 boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800546
Fred Quintana4f9cfc52009-09-02 15:20:23 -0700547 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 Project9066cfe2009-03-03 19:31:44 -0800554 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 Quintanaf892fb32009-08-27 21:32:08 -0700562 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 Project9066cfe2009-03-03 19:31:44 -0800570 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 Quintanad9d2f112009-04-23 13:36:27 -0700581 Account[] accounts;
Fred Quintanaac9385e2009-06-22 18:00:59 -0700582 if (requestedAccount != null) {
583 accounts = new Account[]{requestedAccount};
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800584 } 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 Quintanaac9385e2009-06-22 18:00:59 -0700605 final boolean manualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800606
607 int source;
608 if (uploadOnly) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700609 source = SyncStorageEngine.SOURCE_LOCAL;
Fred Quintanaac9385e2009-06-22 18:00:59 -0700610 } else if (manualSync) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700611 source = SyncStorageEngine.SOURCE_USER;
Fred Quintanaac9385e2009-06-22 18:00:59 -0700612 } else if (requestedAuthority == null) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700613 source = SyncStorageEngine.SOURCE_POLL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800614 } 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 Hackborn231cc602009-04-27 17:10:36 -0700617 source = SyncStorageEngine.SOURCE_SERVER;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800618 }
619
Fred Quintanaac9385e2009-06-22 18:00:59 -0700620 // 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 Quintana718d8a22009-04-29 17:53:20 -0700623 for (RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapter :
624 mSyncAdapters.getAllServices()) {
625 syncableAuthorities.add(syncAdapter.type.authority);
626 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800627
Fred Quintana718d8a22009-04-29 17:53:20 -0700628 // 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 Quintanaac9385e2009-06-22 18:00:59 -0700630 if (requestedAuthority != null) {
Fred Quintana5e787c42009-08-16 23:13:53 -0700631 final boolean hasSyncAdapter = syncableAuthorities.contains(requestedAuthority);
Fred Quintana718d8a22009-04-29 17:53:20 -0700632 syncableAuthorities.clear();
Fred Quintana5e787c42009-08-16 23:13:53 -0700633 if (hasSyncAdapter) syncableAuthorities.add(requestedAuthority);
Fred Quintana718d8a22009-04-29 17:53:20 -0700634 }
635
Fred Quintanaf892fb32009-08-27 21:32:08 -0700636 final boolean masterSyncAutomatically = mSyncStorageEngine.getMasterSyncAutomatically();
637
Fred Quintana718d8a22009-04-29 17:53:20 -0700638 for (String authority : syncableAuthorities) {
Fred Quintanad9d2f112009-04-23 13:36:27 -0700639 for (Account account : accounts) {
Fred Quintana4a6679b2009-08-17 13:05:39 -0700640 int isSyncable = mSyncStorageEngine.getIsSyncable(account, authority);
641 if (isSyncable == 0) {
Fred Quintana5e787c42009-08-16 23:13:53 -0700642 continue;
643 }
Fred Quintana4a6679b2009-08-17 13:05:39 -0700644 if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) {
645 continue;
646 }
Fred Quintanae0616ff2009-08-19 13:13:18 -0700647 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 Quintanaf892fb32009-08-27 21:32:08 -0700654
Fred Quintana4a6679b2009-08-17 13:05:39 -0700655 // make this an initialization sync if the isSyncable state is unknown
Fred Quintanae0616ff2009-08-19 13:13:18 -0700656 Bundle extrasCopy = extras;
Fred Quintanaf892fb32009-08-27 21:32:08 -0700657 long delayCopy = delay;
Fred Quintana4a6679b2009-08-17 13:05:39 -0700658 if (isSyncable < 0) {
Fred Quintanae0616ff2009-08-19 13:13:18 -0700659 extrasCopy = new Bundle(extras);
Fred Quintana4a6679b2009-08-17 13:05:39 -0700660 extrasCopy.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
Fred Quintanaf892fb32009-08-27 21:32:08 -0700661 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 Quintana4a6679b2009-08-17 13:05:39 -0700673 }
Fred Quintanae0616ff2009-08-19 13:13:18 -0700674 if (isLoggable) {
675 Log.v(TAG, "scheduleSync:"
Fred Quintanaf892fb32009-08-27 21:32:08 -0700676 + " delay " + delayCopy
Fred Quintanae0616ff2009-08-19 13:13:18 -0700677 + ", source " + source
678 + ", account " + account
679 + ", authority " + authority
680 + ", extras " + extrasCopy);
681 }
Fred Quintana718d8a22009-04-29 17:53:20 -0700682 scheduleSyncOperation(
Fred Quintanaf892fb32009-08-27 21:32:08 -0700683 new SyncOperation(account, source, authority, extrasCopy, delayCopy));
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800684 }
685 }
686 }
687 }
688
689 private void setStatusText(String message) {
690 mStatusText = message;
691 }
692
Fred Quintanaac9385e2009-06-22 18:00:59 -0700693 public void scheduleLocalSync(Account account, String authority) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800694 final Bundle extras = new Bundle();
695 extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
Fred Quintana4a6679b2009-08-17 13:05:39 -0700696 scheduleSync(account, authority, extras, LOCAL_SYNC_DELAY,
697 false /* onlyThoseWithUnkownSyncableState */);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800698 }
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 Quintanaac9385e2009-06-22 18:00:59 -0700711 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 Project9066cfe2009-03-03 19:31:44 -0800721 }
722
723 public void updateHeartbeatTime() {
724 mHeartbeatTime = SystemClock.elapsedRealtime();
Dianne Hackborn231cc602009-04-27 17:10:36 -0700725 mSyncStorageEngine.reportActiveChange();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800726 }
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 Quintana718d8a22009-04-29 17:53:20 -0700789 long maxSyncRetryTimeInSeconds = Settings.Gservices.getLong(mContext.getContentResolver(),
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800790 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 Quintanaac9385e2009-06-22 18:00:59 -0700803 * 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 Project9066cfe2009-03-03 19:31:44 -0800806 */
Fred Quintanaac9385e2009-06-22 18:00:59 -0700807 public void cancelActiveSync(Account account, String authority) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800808 ActiveSyncContext activeSyncContext = mActiveSyncContext;
809 if (activeSyncContext != null) {
Fred Quintanaac9385e2009-06-22 18:00:59 -0700810 // 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 Project9066cfe2009-03-03 19:31:44 -0800819 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 Quintanaac9385e2009-06-22 18:00:59 -0700870 * 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 Project9066cfe2009-03-03 19:31:44 -0800873 */
Fred Quintanaac9385e2009-06-22 18:00:59 -0700874 public void clearScheduledSyncOperations(Account account, String authority) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800875 synchronized (mSyncQueue) {
Fred Quintanaac9385e2009-06-22 18:00:59 -0700876 mSyncQueue.clear(account, authority);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800877 }
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 Quintanad9d2f112009-04-23 13:36:27 -0700928 final Account account;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800929 int syncSource;
930 String authority;
931 Bundle extras;
932 final String key;
933 long earliestRunTime;
934 long delay;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700935 SyncStorageEngine.PendingOperation pendingOperation;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800936
Fred Quintanad9d2f112009-04-23 13:36:27 -0700937 SyncOperation(Account account, int source, String authority, Bundle extras, long delay) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800938 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 Hackborn231cc602009-04-27 17:10:36 -0700975 if (pendingOperation != null) sb.append(" pendingOperation: ").append(pendingOperation);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800976 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 Quintana718d8a22009-04-29 17:53:20 -07001008 class ActiveSyncContext extends ISyncContext.Stub implements ServiceConnection {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001009 final SyncOperation mSyncOperation;
1010 final long mHistoryRowId;
Fred Quintana718d8a22009-04-29 17:53:20 -07001011 ISyncAdapter mSyncAdapter;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001012 final long mStartTime;
1013 long mTimeoutStartTime;
1014
Fred Quintana718d8a22009-04-29 17:53:20 -07001015 public ActiveSyncContext(SyncOperation syncOperation,
1016 long historyRowId) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001017 super();
1018 mSyncOperation = syncOperation;
1019 mHistoryRowId = historyRowId;
Fred Quintana718d8a22009-04-29 17:53:20 -07001020 mSyncAdapter = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001021 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 Quintana718d8a22009-04-29 17:53:20 -07001046 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 Hackborndd9b82c2009-09-03 00:18:47 -07001067 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 Quintana718d8a22009-04-29 17:53:20 -07001071 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 Project9066cfe2009-03-03 19:31:44 -08001081 @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 Hackborn231cc602009-04-27 17:10:36 -07001091 dumpSyncState(pw, sb);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001092 if (isSyncEnabled()) {
Dianne Hackborn231cc602009-04-27 17:10:36 -07001093 dumpSyncHistory(pw, sb);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001094 }
Fred Quintana718d8a22009-04-29 17:53:20 -07001095
Dianne Hackborn7a135592009-05-06 00:28:37 -07001096 pw.println();
Fred Quintana718d8a22009-04-29 17:53:20 -07001097 pw.println("SyncAdapters:");
1098 for (RegisteredServicesCache.ServiceInfo info : mSyncAdapters.getAllServices()) {
1099 pw.println(" " + info);
1100 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001101 }
1102
Dianne Hackborn231cc602009-04-27 17:10:36 -07001103 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 Project9066cfe2009-03-03 19:31:44 -08001113
Fred Quintanad9d2f112009-04-23 13:36:27 -07001114 final Account[] accounts = mAccounts;
Dianne Hackborn231cc602009-04-27 17:10:36 -07001115 pw.print("accounts: ");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001116 if (accounts != null) {
Dianne Hackborn231cc602009-04-27 17:10:36 -07001117 pw.println(accounts.length);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001118 } else {
Dianne Hackborn231cc602009-04-27 17:10:36 -07001119 pw.println("none");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001120 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001121 final long now = SystemClock.elapsedRealtime();
Dianne Hackborn231cc602009-04-27 17:10:36 -07001122 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 Project9066cfe2009-03-03 19:31:44 -08001131 if (mSyncHandler.mAlarmScheduleTime != null) {
Dianne Hackborn231cc602009-04-27 17:10:36 -07001132 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 Project9066cfe2009-03-03 19:31:44 -08001136 } else {
Dianne Hackborn231cc602009-04-27 17:10:36 -07001137 pw.println("no alarm is scheduled (there had better not be any pending syncs)");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001138 }
1139
Dianne Hackborn231cc602009-04-27 17:10:36 -07001140 pw.print("active sync: "); pw.println(mActiveSyncContext);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001141
Dianne Hackborn231cc602009-04-27 17:10:36 -07001142 pw.print("notification info: ");
1143 sb.setLength(0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001144 mSyncHandler.mSyncNotificationInfo.toString(sb);
Dianne Hackborn231cc602009-04-27 17:10:36 -07001145 pw.println(sb.toString());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001146
1147 synchronized (mSyncQueue) {
Dianne Hackborn231cc602009-04-27 17:10:36 -07001148 pw.print("sync queue: ");
1149 sb.setLength(0);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001150 mSyncQueue.dump(sb);
Dianne Hackborn231cc602009-04-27 17:10:36 -07001151 pw.println(sb.toString());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001152 }
1153
Dianne Hackborn231cc602009-04-27 17:10:36 -07001154 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 Project9066cfe2009-03-03 19:31:44 -08001165 } else {
Dianne Hackborn231cc602009-04-27 17:10:36 -07001166 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 Quintanaffd0cb042009-08-15 21:45:26 -07001178 pw.print(op.account.name); pw.print(":");
1179 pw.print(op.account.type); pw.print(" authority=");
Dianne Hackborn231cc602009-04-27 17:10:36 -07001180 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 Hackborn7a135592009-05-06 00:28:37 -07001189 HashSet<Account> processedAccounts = new HashSet<Account>();
Dianne Hackborn231cc602009-04-27 17:10:36 -07001190 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 Hackborn7a135592009-05-06 00:28:37 -07001201 Account curAccount = authority.account;
Dianne Hackborn231cc602009-04-27 17:10:36 -07001202
1203 if (processedAccounts.contains(curAccount)) {
1204 continue;
1205 }
1206
1207 processedAccounts.add(curAccount);
1208
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001209 pw.print(" Account "); pw.print(authority.account.name);
1210 pw.print(" "); pw.print(authority.account.type);
Dianne Hackborn7a135592009-05-06 00:28:37 -07001211 pw.println(":");
Dianne Hackborn231cc602009-04-27 17:10:36 -07001212 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 Project9066cfe2009-03-03 19:31:44 -08001247 }
1248 }
1249
Dianne Hackborn231cc602009-04-27 17:10:36 -07001250 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 Hackborn7a135592009-05-06 00:28:37 -07001331 if (authority != null) {
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001332 pw.print(authority.account.name);
Dianne Hackborn7a135592009-05-06 00:28:37 -07001333 pw.print(":");
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001334 pw.print(authority.account.type);
Dianne Hackborn7a135592009-05-06 00:28:37 -07001335 pw.print(" ");
1336 pw.print(authority.authority);
1337 } else {
1338 pw.print("<no account>");
1339 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001340 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 Project9066cfe2009-03-03 19:31:44 -08001361 }
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 Quintana718d8a22009-04-29 17:53:20 -07001396 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 Project9066cfe2009-03-03 19:31:44 -08001405 /**
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 Quintana718d8a22009-04-29 17:53:20 -07001414 private static final int MESSAGE_SERVICE_CONNECTED = 4;
1415 private static final int MESSAGE_SERVICE_DISCONNECTED = 5;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001416
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 Quintanad9d2f112009-04-23 13:36:27 -07001430 public Account account;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001431
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 Quintana718d8a22009-04-29 17:53:20 -07001487 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 Quintana21bb0de2009-06-16 10:24:58 -07001514 mActiveSyncContext.mSyncAdapter.cancelSync(mActiveSyncContext);
Fred Quintana718d8a22009-04-29 17:53:20 -07001515 } 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 Project9066cfe2009-03-03 19:31:44 -08001534 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 Quintanad9d2f112009-04-23 13:36:27 -07001632 Account[] accounts = mAccounts;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001633 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 Quintanaac9385e2009-06-22 18:00:59 -07001644 SyncOperation op;
Fred Quintanaf892fb32009-08-27 21:32:08 -07001645 final boolean backgroundDataUsageAllowed =
1646 getConnectivityManager().getBackgroundDataSetting();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001647 synchronized (mSyncQueue) {
1648 while (true) {
Fred Quintanaac9385e2009-06-22 18:00:59 -07001649 op = mSyncQueue.head();
1650 if (op == null) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001651 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 Quintanaac9385e2009-06-22 18:00:59 -07001660 Log.v(TAG, "runStateIdle: sync disabled, dropping " + op);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001661 }
1662 mSyncQueue.popHead();
1663 continue;
1664 }
1665
Fred Quintanaac9385e2009-06-22 18:00:59 -07001666 // 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 Onorato8294fad2009-07-15 16:08:44 -07001671 && mSyncStorageEngine.getMasterSyncAutomatically();
Fred Quintanaac9385e2009-06-22 18:00:59 -07001672 boolean syncAllowed =
1673 manualSync || (backgroundDataUsageAllowed && syncAutomatically);
Fred Quintana4a6679b2009-08-17 13:05:39 -07001674 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 Project9066cfe2009-03-03 19:31:44 -08001684 if (isLoggable) {
Fred Quintanaac9385e2009-06-22 18:00:59 -07001685 Log.v(TAG, "runStateIdle: sync off, dropping " + op);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001686 }
1687 mSyncQueue.popHead();
1688 continue;
1689 }
1690
1691 // skip the sync if the account of this operation no longer exists
Fred Quintanaac9385e2009-06-22 18:00:59 -07001692 if (!ArrayUtils.contains(accounts, op.account)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001693 mSyncQueue.popHead();
1694 if (isLoggable) {
Fred Quintanaac9385e2009-06-22 18:00:59 -07001695 Log.v(TAG, "runStateIdle: account not present, dropping " + op);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001696 }
1697 continue;
1698 }
1699
1700 // go ahead and try to sync this syncOperation
1701 if (isLoggable) {
Fred Quintanaac9385e2009-06-22 18:00:59 -07001702 Log.v(TAG, "runStateIdle: found sync candidate: " + op);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001703 }
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 Quintanaac9385e2009-06-22 18:00:59 -07001710 if (op.earliestRunTime > now) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001711 if (Log.isLoggable(TAG, Log.DEBUG)) {
1712 Log.d(TAG, "runStateIdle: the time is " + now + " yet the next "
Fred Quintanaac9385e2009-06-22 18:00:59 -07001713 + "sync operation is for " + op.earliestRunTime + ": " + op);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001714 }
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 Quintanaac9385e2009-06-22 18:00:59 -07001721 Log.v(TAG, "runStateIdle: we are going to sync " + op);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001722 }
1723 mSyncQueue.popHead();
1724 }
1725
Fred Quintana718d8a22009-04-29 17:53:20 -07001726 // connect to the sync adapter
Fred Quintana4a6679b2009-08-17 13:05:39 -07001727 SyncAdapterType syncAdapterType = SyncAdapterType.newKey(op.authority, op.account.type);
Fred Quintana97889762009-06-15 12:29:24 -07001728 RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
Fred Quintana718d8a22009-04-29 17:53:20 -07001729 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 Project9066cfe2009-03-03 19:31:44 -08001733 }
Fred Quintana718d8a22009-04-29 17:53:20 -07001734 runStateIdle();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001735 return;
1736 }
1737
Fred Quintana718d8a22009-04-29 17:53:20 -07001738 ActiveSyncContext activeSyncContext =
Fred Quintanaac9385e2009-06-22 18:00:59 -07001739 new ActiveSyncContext(op, insertStartSyncEvent(op));
Fred Quintana718d8a22009-04-29 17:53:20 -07001740 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 Project9066cfe2009-03-03 19:31:44 -08001748 mSyncStorageEngine.setActiveSync(mActiveSyncContext);
Fred Quintana718d8a22009-04-29 17:53:20 -07001749 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 Quintana21bb0de2009-06-16 10:24:58 -07001764 syncAdapter.startSync(mActiveSyncContext, syncOperation.authority,
1765 syncOperation.account, syncOperation.extras);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001766 } catch (RemoteException remoteExc) {
1767 if (Config.LOGD) {
1768 Log.d(TAG, "runStateIdle: caught a RemoteException, rescheduling", remoteExc);
1769 }
Fred Quintana718d8a22009-04-29 17:53:20 -07001770 mActiveSyncContext.unBindFromSyncAdapter();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001771 mActiveSyncContext = null;
1772 mSyncStorageEngine.setActiveSync(mActiveSyncContext);
1773 rescheduleWithDelay(syncOperation);
1774 } catch (RuntimeException exc) {
Fred Quintana718d8a22009-04-29 17:53:20 -07001775 mActiveSyncContext.unBindFromSyncAdapter();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001776 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 Project9066cfe2009-03-03 19:31:44 -08001781 }
1782
1783 private void runSyncFinishedOrCanceled(SyncResult syncResult) {
1784 boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
1785 if (isLoggable) Log.v(TAG, "runSyncFinishedOrCanceled");
Fred Quintana718d8a22009-04-29 17:53:20 -07001786 final ActiveSyncContext activeSyncContext = mActiveSyncContext;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001787 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 Hackborn231cc602009-04-27 17:10:36 -07001807 historyMessage = SyncStorageEngine.MESG_SUCCESS;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001808 // 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 Quintana718d8a22009-04-29 17:53:20 -07001826 if (activeSyncContext.mSyncAdapter != null) {
1827 try {
Fred Quintana21bb0de2009-06-16 10:24:58 -07001828 activeSyncContext.mSyncAdapter.cancelSync(activeSyncContext);
Fred Quintana718d8a22009-04-29 17:53:20 -07001829 } catch (RemoteException e) {
1830 // we don't need to retry this in this case
1831 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001832 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001833 historyMessage = SyncStorageEngine.MESG_CANCELED;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001834 downstreamActivity = 0;
1835 upstreamActivity = 0;
1836 }
1837
1838 stopSyncEvent(activeSyncContext.mHistoryRowId, syncOperation, historyMessage,
1839 upstreamActivity, downstreamActivity, elapsedTime);
1840
Fred Quintana718d8a22009-04-29 17:53:20 -07001841 activeSyncContext.unBindFromSyncAdapter();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001842
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 Hackborn231cc602009-04-27 17:10:36 -07001868 if (syncResult.syncAlreadyInProgress)
Fred Quintanaac9385e2009-06-22 18:00:59 -07001869 return ContentResolver.SYNC_ERROR_SYNC_ALREADY_IN_PROGRESS;
Dianne Hackborn231cc602009-04-27 17:10:36 -07001870 if (syncResult.stats.numAuthExceptions > 0)
Fred Quintanaac9385e2009-06-22 18:00:59 -07001871 return ContentResolver.SYNC_ERROR_AUTHENTICATION;
Dianne Hackborn231cc602009-04-27 17:10:36 -07001872 if (syncResult.stats.numIoExceptions > 0)
Fred Quintanaac9385e2009-06-22 18:00:59 -07001873 return ContentResolver.SYNC_ERROR_IO;
Dianne Hackborn231cc602009-04-27 17:10:36 -07001874 if (syncResult.stats.numParseExceptions > 0)
Fred Quintanaac9385e2009-06-22 18:00:59 -07001875 return ContentResolver.SYNC_ERROR_PARSE;
Dianne Hackborn231cc602009-04-27 17:10:36 -07001876 if (syncResult.stats.numConflictDetectedExceptions > 0)
Fred Quintanaac9385e2009-06-22 18:00:59 -07001877 return ContentResolver.SYNC_ERROR_CONFLICT;
Dianne Hackborn231cc602009-04-27 17:10:36 -07001878 if (syncResult.tooManyDeletions)
Fred Quintanaac9385e2009-06-22 18:00:59 -07001879 return ContentResolver.SYNC_ERROR_TOO_MANY_DELETIONS;
Dianne Hackborn231cc602009-04-27 17:10:36 -07001880 if (syncResult.tooManyRetries)
Fred Quintanaac9385e2009-06-22 18:00:59 -07001881 return ContentResolver.SYNC_ERROR_TOO_MANY_RETRIES;
Dianne Hackborn231cc602009-04-27 17:10:36 -07001882 if (syncResult.databaseError)
Fred Quintanaac9385e2009-06-22 18:00:59 -07001883 return ContentResolver.SYNC_ERROR_INTERNAL;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001884 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 Quintanaac9385e2009-06-22 18:00:59 -07001924 // 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 Project9066cfe2009-03-03 19:31:44 -08001928 }
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 Hackborna34f1ad2009-09-02 13:26:28 -07002042 syncStateIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002043 syncStateIntent.putExtra("active", mNeedSyncActiveNotification);
2044 syncStateIntent.putExtra("failing", mNeedSyncErrorNotification);
2045 mContext.sendBroadcast(syncStateIntent);
2046 }
2047
Fred Quintanad9d2f112009-04-23 13:36:27 -07002048 private void installHandleTooManyDeletesNotification(Account account, String authority,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002049 long numDeletes) {
2050 if (mNotificationMgr == null) return;
Fred Quintanac848b702009-08-25 20:18:46 -07002051
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 Project9066cfe2009-03-03 19:31:44 -08002059 Intent clickIntent = new Intent();
2060 clickIntent.setClassName("com.android.providers.subscribedfeeds",
2061 "com.android.settings.SyncActivityTooManyDeletes");
2062 clickIntent.putExtra("account", account);
Fred Quintanac848b702009-08-25 20:18:46 -07002063 clickIntent.putExtra("provider", authorityName.toString());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002064 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 Project9066cfe2009-03-03 19:31:44 -08002077 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 Quintanac848b702009-08-25 20:18:46 -07002083 String.format(tooManyDeletesDescFormat.toString(), authorityName),
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002084 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 Hackborn231cc602009-04-27 17:10:36 -07002114 EventLog.writeEvent(2720, syncOperation.authority,
2115 SyncStorageEngine.EVENT_START, source);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002116
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 Hackborn231cc602009-04-27 17:10:36 -07002123 EventLog.writeEvent(2720, syncOperation.authority,
2124 SyncStorageEngine.EVENT_STOP, syncOperation.syncSource);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002125
2126 mSyncStorageEngine.stopSyncEvent(rowId, elapsedTime, resultMessage,
2127 downstreamActivity, upstreamActivity);
2128 }
2129 }
2130
2131 static class SyncQueue {
2132 private SyncStorageEngine mSyncStorageEngine;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002133
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 Hackborn231cc602009-04-27 17:10:36 -07002146 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 Project9066cfe2009-03-03 19:31:44 -08002155 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07002156
2157 if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002158 }
2159
2160 public boolean add(SyncOperation operation) {
2161 return add(new SyncOperation(operation),
Dianne Hackborn231cc602009-04-27 17:10:36 -07002162 null /* this is not coming from the database */);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002163 }
2164
Dianne Hackborn231cc602009-04-27 17:10:36 -07002165 private boolean add(SyncOperation operation,
2166 SyncStorageEngine.PendingOperation pop) {
2167 if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(pop == null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002168
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 Quintanaac9385e2009-06-22 18:00:59 -07002190 // and this isn't a manual sync operation, ignore this operation
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002191 if (existingOperation != null && existingOperation.delay > 0) {
Fred Quintanaac9385e2009-06-22 18:00:59 -07002192 if (!operation.extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002193 return false;
2194 }
2195 }
2196
2197 if (existingOperation != null
2198 && operation.earliestRunTime >= existingOperation.earliestRunTime) {
Dianne Hackborn231cc602009-04-27 17:10:36 -07002199 if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(pop == null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002200 return false;
2201 }
2202
2203 if (existingOperation != null) {
2204 removeByKey(operationKey);
2205 }
2206
Dianne Hackborn231cc602009-04-27 17:10:36 -07002207 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 Project9066cfe2009-03-03 19:31:44 -08002214 throw new IllegalStateException("error adding pending sync operation "
2215 + operation);
2216 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07002217 operation.pendingOperation = pop;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002218 }
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 Hackborn231cc602009-04-27 17:10:36 -07002227 if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(pop == null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002228 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 Hackborn231cc602009-04-27 17:10:36 -07002239 if (!mSyncStorageEngine.deleteFromPending(operationToRemove.pendingOperation)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002240 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 Hackborn231cc602009-04-27 17:10:36 -07002259 if (!mSyncStorageEngine.deleteFromPending(operation.pendingOperation)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002260 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 Quintanad9d2f112009-04-23 13:36:27 -07002266 public void clear(Account account, String authority) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002267 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 Hackborn231cc602009-04-27 17:10:36 -07002281 if (!mSyncStorageEngine.deleteFromPending(syncOperation.pendingOperation)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002282 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 Hackborn231cc602009-04-27 17:10:36 -07002322 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 Project9066cfe2009-03-03 19:31:44 -08002337 sb.append("\n");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002338 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07002339 dump(sb);
2340 throw new IllegalStateException("DB size mismatch: "
2341 + mOpsByKey.size() + " != " + N + "\n"
2342 + sb.toString());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002343 }
2344 }
2345 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08002346 }
2347}