blob: 64b626ddd155041ffbdc1d01a59a26ab8219d90f [file] [log] [blame]
Dianne Hackborn231cc602009-04-27 17:10:36 -07001/*
2 * Copyright (C) 2009 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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080017package android.content;
18
Dianne Hackborn231cc602009-04-27 17:10:36 -070019import com.android.internal.os.AtomicFile;
20import com.android.internal.util.ArrayUtils;
21import com.android.internal.util.FastXmlSerializer;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022
Dianne Hackborn231cc602009-04-27 17:10:36 -070023import org.xmlpull.v1.XmlPullParser;
24import org.xmlpull.v1.XmlPullParserException;
25import org.xmlpull.v1.XmlSerializer;
26
Fred Quintanad9d2f112009-04-23 13:36:27 -070027import android.accounts.Account;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.database.Cursor;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.database.sqlite.SQLiteDatabase;
Dianne Hackborn231cc602009-04-27 17:10:36 -070030import android.database.sqlite.SQLiteException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.database.sqlite.SQLiteQueryBuilder;
Dianne Hackborn231cc602009-04-27 17:10:36 -070032import android.os.Bundle;
33import android.os.Environment;
34import android.os.Handler;
35import android.os.Message;
36import android.os.Parcel;
37import android.os.RemoteCallbackList;
38import android.os.RemoteException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import android.util.Log;
Dianne Hackborn231cc602009-04-27 17:10:36 -070040import android.util.SparseArray;
41import android.util.Xml;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042
Dianne Hackborn231cc602009-04-27 17:10:36 -070043import java.io.File;
44import java.io.FileInputStream;
45import java.io.FileOutputStream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046import java.util.ArrayList;
Dianne Hackborn231cc602009-04-27 17:10:36 -070047import java.util.Calendar;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048import java.util.HashMap;
Dianne Hackborn231cc602009-04-27 17:10:36 -070049import java.util.Iterator;
50import java.util.TimeZone;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051
Fred Quintanad9d2f112009-04-23 13:36:27 -070052import com.google.android.collect.Sets;
53
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080054/**
Dianne Hackborn231cc602009-04-27 17:10:36 -070055 * Singleton that tracks the sync data and overall sync
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056 * history on the device.
57 *
58 * @hide
59 */
Dianne Hackborn231cc602009-04-27 17:10:36 -070060public class SyncStorageEngine extends Handler {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061 private static final String TAG = "SyncManager";
Dianne Hackborn231cc602009-04-27 17:10:36 -070062 private static final boolean DEBUG = false;
63 private static final boolean DEBUG_FILE = false;
64
65 // @VisibleForTesting
66 static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080067
Dianne Hackborn231cc602009-04-27 17:10:36 -070068 /** Enum value for a sync start event. */
69 public static final int EVENT_START = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080070
Dianne Hackborn231cc602009-04-27 17:10:36 -070071 /** Enum value for a sync stop event. */
72 public static final int EVENT_STOP = 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080073
Dianne Hackborn231cc602009-04-27 17:10:36 -070074 // TODO: i18n -- grab these out of resources.
75 /** String names for the sync event types. */
76 public static final String[] EVENTS = { "START", "STOP" };
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080077
Dianne Hackborn231cc602009-04-27 17:10:36 -070078 /** Enum value for a server-initiated sync. */
79 public static final int SOURCE_SERVER = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080080
Dianne Hackborn231cc602009-04-27 17:10:36 -070081 /** Enum value for a local-initiated sync. */
82 public static final int SOURCE_LOCAL = 1;
83 /**
84 * Enum value for a poll-based sync (e.g., upon connection to
85 * network)
86 */
87 public static final int SOURCE_POLL = 2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088
Dianne Hackborn231cc602009-04-27 17:10:36 -070089 /** Enum value for a user-initiated sync. */
90 public static final int SOURCE_USER = 3;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080091
Dianne Hackborn231cc602009-04-27 17:10:36 -070092 // TODO: i18n -- grab these out of resources.
93 /** String names for the sync source types. */
94 public static final String[] SOURCES = { "SERVER",
95 "LOCAL",
96 "POLL",
97 "USER" };
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080098
Dianne Hackborn231cc602009-04-27 17:10:36 -070099 // Error types
100 public static final int ERROR_SYNC_ALREADY_IN_PROGRESS = 1;
101 public static final int ERROR_AUTHENTICATION = 2;
102 public static final int ERROR_IO = 3;
103 public static final int ERROR_PARSE = 4;
104 public static final int ERROR_CONFLICT = 5;
105 public static final int ERROR_TOO_MANY_DELETIONS = 6;
106 public static final int ERROR_TOO_MANY_RETRIES = 7;
107 public static final int ERROR_INTERNAL = 8;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800108
Dianne Hackborn231cc602009-04-27 17:10:36 -0700109 // The MESG column will contain one of these or one of the Error types.
110 public static final String MESG_SUCCESS = "success";
111 public static final String MESG_CANCELED = "canceled";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800112
Dianne Hackborn231cc602009-04-27 17:10:36 -0700113 public static final int CHANGE_SETTINGS = 1<<0;
114 public static final int CHANGE_PENDING = 1<<1;
115 public static final int CHANGE_ACTIVE = 1<<2;
116 public static final int CHANGE_STATUS = 1<<3;
117 public static final int CHANGE_ALL = 0x7fffffff;
118
119 public static final int MAX_HISTORY = 15;
120
121 private static final int MSG_WRITE_STATUS = 1;
122 private static final long WRITE_STATUS_DELAY = 1000*60*10; // 10 minutes
123
124 private static final int MSG_WRITE_STATISTICS = 2;
125 private static final long WRITE_STATISTICS_DELAY = 1000*60*30; // 1/2 hour
126
127 public static class PendingOperation {
Dianne Hackborn7a135592009-05-06 00:28:37 -0700128 final Account account;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700129 final int syncSource;
130 final String authority;
131 final Bundle extras; // note: read-only.
132
133 int authorityId;
134 byte[] flatExtras;
135
Dianne Hackborn7a135592009-05-06 00:28:37 -0700136 PendingOperation(Account account, int source,
Dianne Hackborn231cc602009-04-27 17:10:36 -0700137 String authority, Bundle extras) {
138 this.account = account;
139 this.syncSource = source;
140 this.authority = authority;
141 this.extras = extras != null ? new Bundle(extras) : extras;
142 this.authorityId = -1;
143 }
144
145 PendingOperation(PendingOperation other) {
146 this.account = other.account;
147 this.syncSource = other.syncSource;
148 this.authority = other.authority;
149 this.extras = other.extras;
150 this.authorityId = other.authorityId;
151 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800152 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700153
154 static class AccountInfo {
Dianne Hackborn7a135592009-05-06 00:28:37 -0700155 final Account account;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700156 final HashMap<String, AuthorityInfo> authorities =
157 new HashMap<String, AuthorityInfo>();
158
Dianne Hackborn7a135592009-05-06 00:28:37 -0700159 AccountInfo(Account account) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700160 this.account = account;
161 }
162 }
163
164 public static class AuthorityInfo {
Dianne Hackborn7a135592009-05-06 00:28:37 -0700165 final Account account;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700166 final String authority;
167 final int ident;
168 boolean enabled;
169
Dianne Hackborn7a135592009-05-06 00:28:37 -0700170 AuthorityInfo(Account account, String authority, int ident) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700171 this.account = account;
172 this.authority = authority;
173 this.ident = ident;
174 enabled = true;
175 }
176 }
177
178 public static class SyncHistoryItem {
179 int authorityId;
180 int historyId;
181 long eventTime;
182 long elapsedTime;
183 int source;
184 int event;
185 long upstreamActivity;
186 long downstreamActivity;
187 String mesg;
188 }
189
190 public static class DayStats {
191 public final int day;
192 public int successCount;
193 public long successTime;
194 public int failureCount;
195 public long failureTime;
196
197 public DayStats(int day) {
198 this.day = day;
199 }
200 }
201
202 // Primary list of all syncable authorities. Also our global lock.
203 private final SparseArray<AuthorityInfo> mAuthorities =
204 new SparseArray<AuthorityInfo>();
205
Dianne Hackborn7a135592009-05-06 00:28:37 -0700206 private final HashMap<Account, AccountInfo> mAccounts =
207 new HashMap<Account, AccountInfo>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800208
Dianne Hackborn231cc602009-04-27 17:10:36 -0700209 private final ArrayList<PendingOperation> mPendingOperations =
210 new ArrayList<PendingOperation>();
211
212 private ActiveSyncInfo mActiveSync;
213
214 private final SparseArray<SyncStatusInfo> mSyncStatus =
215 new SparseArray<SyncStatusInfo>();
216
217 private final ArrayList<SyncHistoryItem> mSyncHistory =
218 new ArrayList<SyncHistoryItem>();
219
220 private final RemoteCallbackList<ISyncStatusObserver> mChangeListeners
221 = new RemoteCallbackList<ISyncStatusObserver>();
222
223 // We keep 4 weeks of stats.
224 private final DayStats[] mDayStats = new DayStats[7*4];
225 private final Calendar mCal;
226 private int mYear;
227 private int mYearInDays;
228
229 private final Context mContext;
230 private static volatile SyncStorageEngine sSyncStorageEngine = null;
231
232 /**
233 * This file contains the core engine state: all accounts and the
234 * settings for them. It must never be lost, and should be changed
235 * infrequently, so it is stored as an XML file.
236 */
237 private final AtomicFile mAccountInfoFile;
238
239 /**
240 * This file contains the current sync status. We would like to retain
241 * it across boots, but its loss is not the end of the world, so we store
242 * this information as binary data.
243 */
244 private final AtomicFile mStatusFile;
245
246 /**
247 * This file contains sync statistics. This is purely debugging information
248 * so is written infrequently and can be thrown away at any time.
249 */
250 private final AtomicFile mStatisticsFile;
251
252 /**
253 * This file contains the pending sync operations. It is a binary file,
254 * which must be updated every time an operation is added or removed,
255 * so we have special handling of it.
256 */
257 private final AtomicFile mPendingFile;
258 private static final int PENDING_FINISH_TO_WRITE = 4;
259 private int mNumPendingFinished = 0;
260
261 private int mNextHistoryId = 0;
262 private boolean mListenForTickles = true;
263
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800264 private SyncStorageEngine(Context context) {
265 mContext = context;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800266 sSyncStorageEngine = this;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700267
268 mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
269
270 File dataDir = Environment.getDataDirectory();
271 File systemDir = new File(dataDir, "system");
272 File syncDir = new File(systemDir, "sync");
273 mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
274 mStatusFile = new AtomicFile(new File(syncDir, "status.bin"));
275 mPendingFile = new AtomicFile(new File(syncDir, "pending.bin"));
276 mStatisticsFile = new AtomicFile(new File(syncDir, "stats.bin"));
277
278 readAccountInfoLocked();
279 readStatusLocked();
280 readPendingOperationsLocked();
281 readStatisticsLocked();
282 readLegacyAccountInfoLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800283 }
284
285 public static SyncStorageEngine newTestInstance(Context context) {
286 return new SyncStorageEngine(context);
287 }
288
289 public static void init(Context context) {
290 if (sSyncStorageEngine != null) {
291 throw new IllegalStateException("already initialized");
292 }
293 sSyncStorageEngine = new SyncStorageEngine(context);
294 }
295
296 public static SyncStorageEngine getSingleton() {
297 if (sSyncStorageEngine == null) {
298 throw new IllegalStateException("not initialized");
299 }
300 return sSyncStorageEngine;
301 }
302
Dianne Hackborn231cc602009-04-27 17:10:36 -0700303 @Override public void handleMessage(Message msg) {
304 if (msg.what == MSG_WRITE_STATUS) {
305 synchronized (mAccounts) {
306 writeStatusLocked();
Fred Quintanad9d2f112009-04-23 13:36:27 -0700307 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700308 } else if (msg.what == MSG_WRITE_STATISTICS) {
309 synchronized (mAccounts) {
310 writeStatisticsLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800311 }
312 }
313 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700314
315 public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
316 synchronized (mAuthorities) {
317 mChangeListeners.register(callback, mask);
318 }
319 }
320
321 public void removeStatusChangeListener(ISyncStatusObserver callback) {
322 synchronized (mAuthorities) {
323 mChangeListeners.unregister(callback);
324 }
325 }
326
327 private void reportChange(int which) {
328 ArrayList<ISyncStatusObserver> reports = null;
329 synchronized (mAuthorities) {
330 int i = mChangeListeners.beginBroadcast();
331 while (i > 0) {
332 i--;
333 Integer mask = (Integer)mChangeListeners.getBroadcastCookie(i);
334 if ((which & mask.intValue()) == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800335 continue;
336 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700337 if (reports == null) {
338 reports = new ArrayList<ISyncStatusObserver>(i);
339 }
340 reports.add(mChangeListeners.getBroadcastItem(i));
341 }
342 }
343
344 if (DEBUG) Log.v(TAG, "reportChange " + which + " to: " + reports);
345
346 if (reports != null) {
347 int i = reports.size();
348 while (i > 0) {
349 i--;
350 try {
351 reports.get(i).onStatusChanged(which);
352 } catch (RemoteException e) {
353 // The remote callback list will take care of this for us.
354 }
355 }
356 }
357 }
358
Dianne Hackborn7a135592009-05-06 00:28:37 -0700359 public boolean getSyncProviderAutomatically(Account account, String providerName) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700360 synchronized (mAuthorities) {
361 if (account != null) {
362 AuthorityInfo authority = getAuthorityLocked(account, providerName,
363 "getSyncProviderAutomatically");
364 return authority != null ? authority.enabled : false;
365 }
366
367 int i = mAuthorities.size();
368 while (i > 0) {
369 i--;
370 AuthorityInfo authority = mAuthorities.get(i);
371 if (authority.authority.equals(providerName)
372 && authority.enabled) {
373 return true;
374 }
375 }
376 return false;
377 }
378 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800379
Dianne Hackborn7a135592009-05-06 00:28:37 -0700380 public void setSyncProviderAutomatically(Account account, String providerName,
381 boolean sync) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700382 synchronized (mAuthorities) {
383 if (account != null) {
384 AuthorityInfo authority = getAuthorityLocked(account, providerName,
385 "setSyncProviderAutomatically");
386 if (authority != null) {
387 authority.enabled = sync;
388 }
389 } else {
390 int i = mAuthorities.size();
391 while (i > 0) {
392 i--;
393 AuthorityInfo authority = mAuthorities.get(i);
394 if (authority.account.equals(account)
395 && authority.authority.equals(providerName)) {
396 authority.enabled = sync;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800397 }
398 }
399 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700400 writeAccountInfoLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800401 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800402
Dianne Hackborn231cc602009-04-27 17:10:36 -0700403 reportChange(CHANGE_SETTINGS);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800404 }
405
Dianne Hackborn231cc602009-04-27 17:10:36 -0700406 public void setListenForNetworkTickles(boolean flag) {
407 synchronized (mAuthorities) {
408 mListenForTickles = flag;
409 writeAccountInfoLocked();
410 }
411 reportChange(CHANGE_SETTINGS);
412 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800413
Dianne Hackborn231cc602009-04-27 17:10:36 -0700414 public boolean getListenForNetworkTickles() {
415 synchronized (mAuthorities) {
416 return mListenForTickles;
417 }
418 }
419
Dianne Hackborn7a135592009-05-06 00:28:37 -0700420 public AuthorityInfo getAuthority(Account account, String authority) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700421 synchronized (mAuthorities) {
422 return getAuthorityLocked(account, authority, null);
423 }
424 }
425
426 public AuthorityInfo getAuthority(int authorityId) {
427 synchronized (mAuthorities) {
428 return mAuthorities.get(authorityId);
429 }
430 }
431
432 /**
433 * Returns true if there is currently a sync operation for the given
434 * account or authority in the pending list, or actively being processed.
435 */
Dianne Hackborn7a135592009-05-06 00:28:37 -0700436 public boolean isSyncActive(Account account, String authority) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700437 synchronized (mAuthorities) {
438 int i = mPendingOperations.size();
439 while (i > 0) {
440 i--;
441 // TODO(fredq): this probably shouldn't be considering
442 // pending operations.
443 PendingOperation op = mPendingOperations.get(i);
444 if (op.account.equals(account) && op.authority.equals(authority)) {
445 return true;
446 }
447 }
448
449 if (mActiveSync != null) {
450 AuthorityInfo ainfo = getAuthority(mActiveSync.authorityId);
451 if (ainfo != null && ainfo.account.equals(account)
452 && ainfo.authority.equals(authority)) {
453 return true;
454 }
455 }
456 }
457
458 return false;
459 }
460
461 public PendingOperation insertIntoPending(PendingOperation op) {
462 synchronized (mAuthorities) {
463 if (DEBUG) Log.v(TAG, "insertIntoPending: account=" + op.account
464 + " auth=" + op.authority
465 + " src=" + op.syncSource
466 + " extras=" + op.extras);
467
468 AuthorityInfo authority = getOrCreateAuthorityLocked(op.account,
469 op.authority,
470 -1 /* desired identifier */,
471 true /* write accounts to storage */);
472 if (authority == null) {
473 return null;
474 }
475
476 op = new PendingOperation(op);
477 op.authorityId = authority.ident;
478 mPendingOperations.add(op);
479 appendPendingOperationLocked(op);
480
481 SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
482 status.pending = true;
483 }
484
485 reportChange(CHANGE_PENDING);
486 return op;
487 }
488
489 public boolean deleteFromPending(PendingOperation op) {
490 boolean res = false;
491 synchronized (mAuthorities) {
492 if (DEBUG) Log.v(TAG, "deleteFromPending: account=" + op.account
493 + " auth=" + op.authority
494 + " src=" + op.syncSource
495 + " extras=" + op.extras);
496 if (mPendingOperations.remove(op)) {
497 if (mPendingOperations.size() == 0
498 || mNumPendingFinished >= PENDING_FINISH_TO_WRITE) {
499 writePendingOperationsLocked();
500 mNumPendingFinished = 0;
501 } else {
502 mNumPendingFinished++;
503 }
504
505 AuthorityInfo authority = getAuthorityLocked(op.account, op.authority,
506 "deleteFromPending");
507 if (authority != null) {
508 if (DEBUG) Log.v(TAG, "removing - " + authority);
509 final int N = mPendingOperations.size();
510 boolean morePending = false;
511 for (int i=0; i<N; i++) {
512 PendingOperation cur = mPendingOperations.get(i);
513 if (cur.account.equals(op.account)
514 && cur.authority.equals(op.authority)) {
515 morePending = true;
516 break;
517 }
518 }
519
520 if (!morePending) {
521 if (DEBUG) Log.v(TAG, "no more pending!");
522 SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
523 status.pending = false;
524 }
525 }
526
527 res = true;
528 }
529 }
530
531 reportChange(CHANGE_PENDING);
532 return res;
533 }
534
535 public int clearPending() {
536 int num;
537 synchronized (mAuthorities) {
538 if (DEBUG) Log.v(TAG, "clearPending");
539 num = mPendingOperations.size();
540 mPendingOperations.clear();
541 final int N = mSyncStatus.size();
542 for (int i=0; i<N; i++) {
543 mSyncStatus.get(i).pending = false;
544 }
545 writePendingOperationsLocked();
546 }
547 reportChange(CHANGE_PENDING);
548 return num;
549 }
550
551 /**
552 * Return a copy of the current array of pending operations. The
553 * PendingOperation objects are the real objects stored inside, so that
554 * they can be used with deleteFromPending().
555 */
556 public ArrayList<PendingOperation> getPendingOperations() {
557 synchronized (mAuthorities) {
558 return new ArrayList<PendingOperation>(mPendingOperations);
559 }
560 }
561
562 /**
563 * Return the number of currently pending operations.
564 */
565 public int getPendingOperationCount() {
566 synchronized (mAuthorities) {
567 return mPendingOperations.size();
568 }
569 }
570
571 /**
572 * Called when the set of account has changed, given the new array of
573 * active accounts.
574 */
Dianne Hackborn7a135592009-05-06 00:28:37 -0700575 public void doDatabaseCleanup(Account[] accounts) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700576 synchronized (mAuthorities) {
577 if (DEBUG) Log.w(TAG, "Updating for new accounts...");
578 SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>();
579 Iterator<AccountInfo> accIt = mAccounts.values().iterator();
580 while (accIt.hasNext()) {
581 AccountInfo acc = accIt.next();
582 if (!ArrayUtils.contains(accounts, acc.account)) {
583 // This account no longer exists...
584 if (DEBUG) Log.w(TAG, "Account removed: " + acc.account);
585 for (AuthorityInfo auth : acc.authorities.values()) {
586 removing.put(auth.ident, auth);
587 }
588 accIt.remove();
589 }
590 }
591
592 // Clean out all data structures.
593 int i = removing.size();
594 if (i > 0) {
595 while (i > 0) {
596 i--;
597 int ident = removing.keyAt(i);
598 mAuthorities.remove(ident);
599 int j = mSyncStatus.size();
600 while (j > 0) {
601 j--;
602 if (mSyncStatus.keyAt(j) == ident) {
603 mSyncStatus.remove(mSyncStatus.keyAt(j));
604 }
605 }
606 j = mSyncHistory.size();
607 while (j > 0) {
608 j--;
609 if (mSyncHistory.get(j).authorityId == ident) {
610 mSyncHistory.remove(j);
611 }
612 }
613 }
614 writeAccountInfoLocked();
615 writeStatusLocked();
616 writePendingOperationsLocked();
617 writeStatisticsLocked();
618 }
619 }
620 }
621
622 /**
623 * Called when the currently active sync is changing (there can only be
624 * one at a time). Either supply a valid ActiveSyncContext with information
625 * about the sync, or null to stop the currently active sync.
626 */
627 public void setActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
628 synchronized (mAuthorities) {
629 if (activeSyncContext != null) {
630 if (DEBUG) Log.v(TAG, "setActiveSync: account="
631 + activeSyncContext.mSyncOperation.account
632 + " auth=" + activeSyncContext.mSyncOperation.authority
633 + " src=" + activeSyncContext.mSyncOperation.syncSource
634 + " extras=" + activeSyncContext.mSyncOperation.extras);
635 if (mActiveSync != null) {
636 Log.w(TAG, "setActiveSync called with existing active sync!");
637 }
638 AuthorityInfo authority = getAuthorityLocked(
639 activeSyncContext.mSyncOperation.account,
640 activeSyncContext.mSyncOperation.authority,
641 "setActiveSync");
642 if (authority == null) {
643 return;
644 }
645 mActiveSync = new ActiveSyncInfo(authority.ident,
646 authority.account, authority.authority,
647 activeSyncContext.mStartTime);
648 } else {
649 if (DEBUG) Log.v(TAG, "setActiveSync: null");
650 mActiveSync = null;
651 }
652 }
653
654 reportChange(CHANGE_ACTIVE);
655 }
656
657 /**
658 * To allow others to send active change reports, to poke clients.
659 */
660 public void reportActiveChange() {
661 reportChange(CHANGE_ACTIVE);
662 }
663
664 /**
665 * Note that sync has started for the given account and authority.
666 */
Dianne Hackborn7a135592009-05-06 00:28:37 -0700667 public long insertStartSyncEvent(Account accountName, String authorityName,
Dianne Hackborn231cc602009-04-27 17:10:36 -0700668 long now, int source) {
669 long id;
670 synchronized (mAuthorities) {
671 if (DEBUG) Log.v(TAG, "insertStartSyncEvent: account=" + accountName
672 + " auth=" + authorityName + " source=" + source);
673 AuthorityInfo authority = getAuthorityLocked(accountName, authorityName,
674 "insertStartSyncEvent");
675 if (authority == null) {
676 return -1;
677 }
678 SyncHistoryItem item = new SyncHistoryItem();
679 item.authorityId = authority.ident;
680 item.historyId = mNextHistoryId++;
681 if (mNextHistoryId < 0) mNextHistoryId = 0;
682 item.eventTime = now;
683 item.source = source;
684 item.event = EVENT_START;
685 mSyncHistory.add(0, item);
686 while (mSyncHistory.size() > MAX_HISTORY) {
687 mSyncHistory.remove(mSyncHistory.size()-1);
688 }
689 id = item.historyId;
690 if (DEBUG) Log.v(TAG, "returning historyId " + id);
691 }
692
693 reportChange(CHANGE_STATUS);
694 return id;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800695 }
696
697 public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage,
698 long downstreamActivity, long upstreamActivity) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700699 synchronized (mAuthorities) {
700 if (DEBUG) Log.v(TAG, "stopSyncEvent: historyId=" + historyId);
701 SyncHistoryItem item = null;
702 int i = mSyncHistory.size();
703 while (i > 0) {
704 i--;
705 item = mSyncHistory.get(i);
706 if (item.historyId == historyId) {
707 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800708 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700709 item = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800710 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700711
712 if (item == null) {
713 Log.w(TAG, "stopSyncEvent: no history for id " + historyId);
714 return;
715 }
716
717 item.elapsedTime = elapsedTime;
718 item.event = EVENT_STOP;
719 item.mesg = resultMessage;
720 item.downstreamActivity = downstreamActivity;
721 item.upstreamActivity = upstreamActivity;
722
723 SyncStatusInfo status = getOrCreateSyncStatusLocked(item.authorityId);
724
725 status.numSyncs++;
726 status.totalElapsedTime += elapsedTime;
727 switch (item.source) {
728 case SOURCE_LOCAL:
729 status.numSourceLocal++;
730 break;
731 case SOURCE_POLL:
732 status.numSourcePoll++;
733 break;
734 case SOURCE_USER:
735 status.numSourceUser++;
736 break;
737 case SOURCE_SERVER:
738 status.numSourceServer++;
739 break;
740 }
741
742 boolean writeStatisticsNow = false;
743 int day = getCurrentDay();
744 if (mDayStats[0] == null) {
745 mDayStats[0] = new DayStats(day);
746 } else if (day != mDayStats[0].day) {
747 System.arraycopy(mDayStats, 0, mDayStats, 1, mDayStats.length-1);
748 mDayStats[0] = new DayStats(day);
749 writeStatisticsNow = true;
750 } else if (mDayStats[0] == null) {
751 }
752 final DayStats ds = mDayStats[0];
753
754 final long lastSyncTime = (item.eventTime + elapsedTime);
755 boolean writeStatusNow = false;
756 if (MESG_SUCCESS.equals(resultMessage)) {
757 // - if successful, update the successful columns
758 if (status.lastSuccessTime == 0 || status.lastFailureTime != 0) {
759 writeStatusNow = true;
760 }
761 status.lastSuccessTime = lastSyncTime;
762 status.lastSuccessSource = item.source;
763 status.lastFailureTime = 0;
764 status.lastFailureSource = -1;
765 status.lastFailureMesg = null;
766 status.initialFailureTime = 0;
767 ds.successCount++;
768 ds.successTime += elapsedTime;
769 } else if (!MESG_CANCELED.equals(resultMessage)) {
770 if (status.lastFailureTime == 0) {
771 writeStatusNow = true;
772 }
773 status.lastFailureTime = lastSyncTime;
774 status.lastFailureSource = item.source;
775 status.lastFailureMesg = resultMessage;
776 if (status.initialFailureTime == 0) {
777 status.initialFailureTime = lastSyncTime;
778 }
779 ds.failureCount++;
780 ds.failureTime += elapsedTime;
781 }
782
783 if (writeStatusNow) {
784 writeStatusLocked();
785 } else if (!hasMessages(MSG_WRITE_STATUS)) {
786 sendMessageDelayed(obtainMessage(MSG_WRITE_STATUS),
787 WRITE_STATUS_DELAY);
788 }
789 if (writeStatisticsNow) {
790 writeStatisticsLocked();
791 } else if (!hasMessages(MSG_WRITE_STATISTICS)) {
792 sendMessageDelayed(obtainMessage(MSG_WRITE_STATISTICS),
793 WRITE_STATISTICS_DELAY);
794 }
795 }
796
797 reportChange(CHANGE_STATUS);
798 }
799
800 /**
801 * Return the currently active sync information, or null if there is no
802 * active sync. Note that the returned object is the real, live active
803 * sync object, so be careful what you do with it.
804 */
805 public ActiveSyncInfo getActiveSync() {
806 synchronized (mAuthorities) {
807 return mActiveSync;
808 }
809 }
810
811 /**
812 * Return an array of the current sync status for all authorities. Note
813 * that the objects inside the array are the real, live status objects,
814 * so be careful what you do with them.
815 */
816 public ArrayList<SyncStatusInfo> getSyncStatus() {
817 synchronized (mAuthorities) {
818 final int N = mSyncStatus.size();
819 ArrayList<SyncStatusInfo> ops = new ArrayList<SyncStatusInfo>(N);
820 for (int i=0; i<N; i++) {
821 ops.add(mSyncStatus.valueAt(i));
822 }
823 return ops;
824 }
825 }
826
827 /**
828 * Returns the status that matches the authority. If there are multiples accounts for
829 * the authority, the one with the latest "lastSuccessTime" status is returned.
830 * @param authority the authority whose row should be selected
831 * @return the SyncStatusInfo for the authority, or null if none exists
832 */
833 public SyncStatusInfo getStatusByAuthority(String authority) {
834 synchronized (mAuthorities) {
835 SyncStatusInfo best = null;
836 final int N = mSyncStatus.size();
837 for (int i=0; i<N; i++) {
838 SyncStatusInfo cur = mSyncStatus.get(i);
839 AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
840 if (ainfo != null && ainfo.authority.equals(authority)) {
841 if (best == null) {
842 best = cur;
843 } else if (best.lastSuccessTime > cur.lastSuccessTime) {
844 best = cur;
845 }
846 }
847 }
848 return best;
849 }
850 }
851
852 /**
853 * Return true if the pending status is true of any matching authorities.
854 */
Dianne Hackborn7a135592009-05-06 00:28:37 -0700855 public boolean isAuthorityPending(Account account, String authority) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700856 synchronized (mAuthorities) {
857 final int N = mSyncStatus.size();
858 for (int i=0; i<N; i++) {
859 SyncStatusInfo cur = mSyncStatus.get(i);
860 AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
861 if (ainfo == null) {
862 continue;
863 }
864 if (account != null && !ainfo.account.equals(account)) {
865 continue;
866 }
867 if (ainfo.authority.equals(authority) && cur.pending) {
868 return true;
869 }
870 }
871 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800872 }
873 }
874
875 /**
Dianne Hackborn231cc602009-04-27 17:10:36 -0700876 * Return an array of the current sync status for all authorities. Note
877 * that the objects inside the array are the real, live status objects,
878 * so be careful what you do with them.
879 */
880 public ArrayList<SyncHistoryItem> getSyncHistory() {
881 synchronized (mAuthorities) {
882 final int N = mSyncHistory.size();
883 ArrayList<SyncHistoryItem> items = new ArrayList<SyncHistoryItem>(N);
884 for (int i=0; i<N; i++) {
885 items.add(mSyncHistory.get(i));
886 }
887 return items;
888 }
889 }
890
891 /**
892 * Return an array of the current per-day statistics. Note
893 * that the objects inside the array are the real, live status objects,
894 * so be careful what you do with them.
895 */
896 public DayStats[] getDayStatistics() {
897 synchronized (mAuthorities) {
898 DayStats[] ds = new DayStats[mDayStats.length];
899 System.arraycopy(mDayStats, 0, ds, 0, ds.length);
900 return ds;
901 }
902 }
903
904 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800905 * If sync is failing for any of the provider/accounts then determine the time at which it
906 * started failing and return the earliest time over all the provider/accounts. If none are
907 * failing then return 0.
908 */
909 public long getInitialSyncFailureTime() {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700910 synchronized (mAuthorities) {
911 if (!mListenForTickles) {
912 return 0;
913 }
914
915 long oldest = 0;
916 int i = mSyncStatus.size();
917 while (i > 0) {
918 i--;
919 SyncStatusInfo stats = mSyncStatus.valueAt(i);
920 AuthorityInfo authority = mAuthorities.get(stats.authorityId);
921 if (authority != null && authority.enabled) {
922 if (oldest == 0 || stats.initialFailureTime < oldest) {
923 oldest = stats.initialFailureTime;
924 }
925 }
926 }
927
928 return oldest;
929 }
930 }
931
932 private int getCurrentDay() {
933 mCal.setTimeInMillis(System.currentTimeMillis());
934 final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR);
935 if (mYear != mCal.get(Calendar.YEAR)) {
936 mYear = mCal.get(Calendar.YEAR);
937 mCal.clear();
938 mCal.set(Calendar.YEAR, mYear);
939 mYearInDays = (int)(mCal.getTimeInMillis()/86400000);
940 }
941 return dayOfYear + mYearInDays;
942 }
943
944 /**
945 * Retrieve an authority, returning null if one does not exist.
946 *
947 * @param accountName The name of the account for the authority.
948 * @param authorityName The name of the authority itself.
949 * @param tag If non-null, this will be used in a log message if the
950 * requested authority does not exist.
951 */
Dianne Hackborn7a135592009-05-06 00:28:37 -0700952 private AuthorityInfo getAuthorityLocked(Account accountName, String authorityName,
Dianne Hackborn231cc602009-04-27 17:10:36 -0700953 String tag) {
954 AccountInfo account = mAccounts.get(accountName);
955 if (account == null) {
956 if (tag != null) {
957 Log.w(TAG, tag + ": unknown account " + accountName);
958 }
959 return null;
960 }
961 AuthorityInfo authority = account.authorities.get(authorityName);
962 if (authority == null) {
963 if (tag != null) {
964 Log.w(TAG, tag + ": unknown authority " + authorityName);
965 }
966 return null;
967 }
968
969 return authority;
970 }
971
Dianne Hackborn7a135592009-05-06 00:28:37 -0700972 private AuthorityInfo getOrCreateAuthorityLocked(Account accountName,
Dianne Hackborn231cc602009-04-27 17:10:36 -0700973 String authorityName, int ident, boolean doWrite) {
974 AccountInfo account = mAccounts.get(accountName);
975 if (account == null) {
976 account = new AccountInfo(accountName);
977 mAccounts.put(accountName, account);
978 }
979 AuthorityInfo authority = account.authorities.get(authorityName);
980 if (authority == null) {
981 if (ident < 0) {
982 // Look for a new identifier for this authority.
983 final int N = mAuthorities.size();
984 ident = 0;
985 for (int i=0; i<N; i++) {
986 if (mAuthorities.valueAt(i).ident > ident) {
987 break;
988 }
989 ident++;
990 }
991 }
992 authority = new AuthorityInfo(accountName, authorityName, ident);
993 account.authorities.put(authorityName, authority);
994 mAuthorities.put(ident, authority);
995 if (doWrite) {
996 writeAccountInfoLocked();
997 }
998 }
999
1000 return authority;
1001 }
1002
1003 private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) {
1004 SyncStatusInfo status = mSyncStatus.get(authorityId);
1005 if (status == null) {
1006 status = new SyncStatusInfo(authorityId);
1007 mSyncStatus.put(authorityId, status);
1008 }
1009 return status;
1010 }
1011
1012 /**
1013 * Read all account information back in to the initial engine state.
1014 */
1015 private void readAccountInfoLocked() {
1016 FileInputStream fis = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001017 try {
Dianne Hackborn231cc602009-04-27 17:10:36 -07001018 fis = mAccountInfoFile.openRead();
1019 if (DEBUG_FILE) Log.v(TAG, "Reading " + mAccountInfoFile.getBaseFile());
1020 XmlPullParser parser = Xml.newPullParser();
1021 parser.setInput(fis, null);
1022 int eventType = parser.getEventType();
1023 while (eventType != XmlPullParser.START_TAG) {
1024 eventType = parser.next();
1025 }
1026 String tagName = parser.getName();
1027 if ("accounts".equals(tagName)) {
1028 String listen = parser.getAttributeValue(
1029 null, "listen-for-tickles");
1030 mListenForTickles = listen == null
1031 || Boolean.parseBoolean(listen);
1032 eventType = parser.next();
1033 do {
1034 if (eventType == XmlPullParser.START_TAG
1035 && parser.getDepth() == 2) {
1036 tagName = parser.getName();
1037 if ("authority".equals(tagName)) {
1038 int id = -1;
1039 try {
1040 id = Integer.parseInt(parser.getAttributeValue(
1041 null, "id"));
1042 } catch (NumberFormatException e) {
1043 } catch (NullPointerException e) {
1044 }
1045 if (id >= 0) {
1046 String accountName = parser.getAttributeValue(
1047 null, "account");
Dianne Hackborn7a135592009-05-06 00:28:37 -07001048 String accountType = parser.getAttributeValue(
1049 null, "type");
1050 if (accountType == null) {
1051 accountType = "com.google.GAIA";
1052 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001053 String authorityName = parser.getAttributeValue(
1054 null, "authority");
1055 String enabled = parser.getAttributeValue(
1056 null, "enabled");
1057 AuthorityInfo authority = mAuthorities.get(id);
1058 if (DEBUG_FILE) Log.v(TAG, "Adding authority: account="
1059 + accountName + " auth=" + authorityName
1060 + " enabled=" + enabled);
1061 if (authority == null) {
1062 if (DEBUG_FILE) Log.v(TAG, "Creating entry");
1063 authority = getOrCreateAuthorityLocked(
Dianne Hackborn7a135592009-05-06 00:28:37 -07001064 new Account(accountName, accountType),
1065 authorityName, id, false);
Dianne Hackborn231cc602009-04-27 17:10:36 -07001066 }
1067 if (authority != null) {
1068 authority.enabled = enabled == null
1069 || Boolean.parseBoolean(enabled);
1070 } else {
1071 Log.w(TAG, "Failure adding authority: account="
1072 + accountName + " auth=" + authorityName
1073 + " enabled=" + enabled);
1074 }
1075 }
1076 }
1077 }
1078 eventType = parser.next();
1079 } while (eventType != XmlPullParser.END_DOCUMENT);
1080 }
1081 } catch (XmlPullParserException e) {
1082 Log.w(TAG, "Error reading accounts", e);
1083 } catch (java.io.IOException e) {
1084 if (fis == null) Log.i(TAG, "No initial accounts");
1085 else Log.w(TAG, "Error reading accounts", e);
1086 } finally {
1087 if (fis != null) {
1088 try {
1089 fis.close();
1090 } catch (java.io.IOException e1) {
1091 }
1092 }
1093 }
1094 }
1095
1096 /**
1097 * Write all account information to the account file.
1098 */
1099 private void writeAccountInfoLocked() {
1100 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mAccountInfoFile.getBaseFile());
1101 FileOutputStream fos = null;
1102
1103 try {
1104 fos = mAccountInfoFile.startWrite();
1105 XmlSerializer out = new FastXmlSerializer();
1106 out.setOutput(fos, "utf-8");
1107 out.startDocument(null, true);
1108 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
1109
1110 out.startTag(null, "accounts");
1111 if (!mListenForTickles) {
1112 out.attribute(null, "listen-for-tickles", "false");
1113 }
1114
1115 final int N = mAuthorities.size();
1116 for (int i=0; i<N; i++) {
1117 AuthorityInfo authority = mAuthorities.get(i);
1118 out.startTag(null, "authority");
1119 out.attribute(null, "id", Integer.toString(authority.ident));
Dianne Hackborn7a135592009-05-06 00:28:37 -07001120 out.attribute(null, "account", authority.account.mName);
1121 out.attribute(null, "type", authority.account.mType);
Dianne Hackborn231cc602009-04-27 17:10:36 -07001122 out.attribute(null, "authority", authority.authority);
1123 if (!authority.enabled) {
1124 out.attribute(null, "enabled", "false");
1125 }
1126 out.endTag(null, "authority");
1127 }
1128
1129 out.endTag(null, "accounts");
1130
1131 out.endDocument();
1132
1133 mAccountInfoFile.finishWrite(fos);
1134 } catch (java.io.IOException e1) {
1135 Log.w(TAG, "Error writing accounts", e1);
1136 if (fos != null) {
1137 mAccountInfoFile.failWrite(fos);
1138 }
1139 }
1140 }
1141
1142 static int getIntColumn(Cursor c, String name) {
1143 return c.getInt(c.getColumnIndex(name));
1144 }
1145
1146 static long getLongColumn(Cursor c, String name) {
1147 return c.getLong(c.getColumnIndex(name));
1148 }
1149
1150 /**
1151 * Load sync engine state from the old syncmanager database, and then
1152 * erase it. Note that we don't deal with pending operations, active
1153 * sync, or history.
1154 */
1155 private void readLegacyAccountInfoLocked() {
1156 // Look for old database to initialize from.
1157 File file = mContext.getDatabasePath("syncmanager.db");
1158 if (!file.exists()) {
1159 return;
1160 }
1161 String path = file.getPath();
1162 SQLiteDatabase db = null;
1163 try {
1164 db = SQLiteDatabase.openDatabase(path, null,
1165 SQLiteDatabase.OPEN_READONLY);
1166 } catch (SQLiteException e) {
1167 }
1168
1169 if (db != null) {
1170 // Copy in all of the status information, as well as accounts.
1171 if (DEBUG_FILE) Log.v(TAG, "Reading legacy sync accounts db");
1172 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
1173 qb.setTables("stats, status");
1174 HashMap<String,String> map = new HashMap<String,String>();
1175 map.put("_id", "status._id as _id");
1176 map.put("account", "stats.account as account");
Dianne Hackborn7a135592009-05-06 00:28:37 -07001177 map.put("type", "stats.type as type");
Dianne Hackborn231cc602009-04-27 17:10:36 -07001178 map.put("authority", "stats.authority as authority");
1179 map.put("totalElapsedTime", "totalElapsedTime");
1180 map.put("numSyncs", "numSyncs");
1181 map.put("numSourceLocal", "numSourceLocal");
1182 map.put("numSourcePoll", "numSourcePoll");
1183 map.put("numSourceServer", "numSourceServer");
1184 map.put("numSourceUser", "numSourceUser");
1185 map.put("lastSuccessSource", "lastSuccessSource");
1186 map.put("lastSuccessTime", "lastSuccessTime");
1187 map.put("lastFailureSource", "lastFailureSource");
1188 map.put("lastFailureTime", "lastFailureTime");
1189 map.put("lastFailureMesg", "lastFailureMesg");
1190 map.put("pending", "pending");
1191 qb.setProjectionMap(map);
1192 qb.appendWhere("stats._id = status.stats_id");
1193 Cursor c = qb.query(db, null, null, null, null, null, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001194 while (c.moveToNext()) {
Dianne Hackborn231cc602009-04-27 17:10:36 -07001195 String accountName = c.getString(c.getColumnIndex("account"));
Dianne Hackborn7a135592009-05-06 00:28:37 -07001196 String accountType = c.getString(c.getColumnIndex("type"));
1197 if (accountType == null) {
1198 accountType = "com.google.GAIA";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001199 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001200 String authorityName = c.getString(c.getColumnIndex("authority"));
1201 AuthorityInfo authority = this.getOrCreateAuthorityLocked(
Dianne Hackborn7a135592009-05-06 00:28:37 -07001202 new Account(accountName, accountType),
1203 authorityName, -1, false);
Dianne Hackborn231cc602009-04-27 17:10:36 -07001204 if (authority != null) {
1205 int i = mSyncStatus.size();
1206 boolean found = false;
1207 SyncStatusInfo st = null;
1208 while (i > 0) {
1209 i--;
1210 st = mSyncStatus.get(i);
1211 if (st.authorityId == authority.ident) {
1212 found = true;
1213 break;
1214 }
1215 }
1216 if (!found) {
1217 st = new SyncStatusInfo(authority.ident);
1218 mSyncStatus.put(authority.ident, st);
1219 }
1220 st.totalElapsedTime = getLongColumn(c, "totalElapsedTime");
1221 st.numSyncs = getIntColumn(c, "numSyncs");
1222 st.numSourceLocal = getIntColumn(c, "numSourceLocal");
1223 st.numSourcePoll = getIntColumn(c, "numSourcePoll");
1224 st.numSourceServer = getIntColumn(c, "numSourceServer");
1225 st.numSourceUser = getIntColumn(c, "numSourceUser");
1226 st.lastSuccessSource = getIntColumn(c, "lastSuccessSource");
1227 st.lastSuccessTime = getLongColumn(c, "lastSuccessTime");
1228 st.lastFailureSource = getIntColumn(c, "lastFailureSource");
1229 st.lastFailureTime = getLongColumn(c, "lastFailureTime");
1230 st.lastFailureMesg = c.getString(c.getColumnIndex("lastFailureMesg"));
1231 st.pending = getIntColumn(c, "pending") != 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001232 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001233 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001234
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001235 c.close();
Dianne Hackborn231cc602009-04-27 17:10:36 -07001236
1237 // Retrieve the settings.
1238 qb = new SQLiteQueryBuilder();
1239 qb.setTables("settings");
1240 c = qb.query(db, null, null, null, null, null, null);
1241 while (c.moveToNext()) {
1242 String name = c.getString(c.getColumnIndex("name"));
1243 String value = c.getString(c.getColumnIndex("value"));
1244 if (name == null) continue;
1245 if (name.equals("listen_for_tickles")) {
1246 setListenForNetworkTickles(value == null
1247 || Boolean.parseBoolean(value));
1248 } else if (name.startsWith("sync_provider_")) {
1249 String provider = name.substring("sync_provider_".length(),
1250 name.length());
1251 setSyncProviderAutomatically(null, provider,
1252 value == null || Boolean.parseBoolean(value));
1253 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001254 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001255
1256 c.close();
1257
1258 db.close();
1259
1260 writeAccountInfoLocked();
1261 writeStatusLocked();
1262 (new File(path)).delete();
1263 }
1264 }
1265
1266 public static final int STATUS_FILE_END = 0;
1267 public static final int STATUS_FILE_ITEM = 100;
1268
1269 /**
1270 * Read all sync status back in to the initial engine state.
1271 */
1272 private void readStatusLocked() {
1273 if (DEBUG_FILE) Log.v(TAG, "Reading " + mStatusFile.getBaseFile());
1274 try {
1275 byte[] data = mStatusFile.readFully();
1276 Parcel in = Parcel.obtain();
1277 in.unmarshall(data, 0, data.length);
1278 in.setDataPosition(0);
1279 int token;
1280 while ((token=in.readInt()) != STATUS_FILE_END) {
1281 if (token == STATUS_FILE_ITEM) {
1282 SyncStatusInfo status = new SyncStatusInfo(in);
1283 if (mAuthorities.indexOfKey(status.authorityId) >= 0) {
1284 status.pending = false;
1285 if (DEBUG_FILE) Log.v(TAG, "Adding status for id "
1286 + status.authorityId);
1287 mSyncStatus.put(status.authorityId, status);
1288 }
1289 } else {
1290 // Ooops.
1291 Log.w(TAG, "Unknown status token: " + token);
1292 break;
1293 }
1294 }
1295 } catch (java.io.IOException e) {
1296 Log.i(TAG, "No initial status");
1297 }
1298 }
1299
1300 /**
1301 * Write all sync status to the sync status file.
1302 */
1303 private void writeStatusLocked() {
1304 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatusFile.getBaseFile());
1305
1306 // The file is being written, so we don't need to have a scheduled
1307 // write until the next change.
1308 removeMessages(MSG_WRITE_STATUS);
1309
1310 FileOutputStream fos = null;
1311 try {
1312 fos = mStatusFile.startWrite();
1313 Parcel out = Parcel.obtain();
1314 final int N = mSyncStatus.size();
1315 for (int i=0; i<N; i++) {
1316 SyncStatusInfo status = mSyncStatus.valueAt(i);
1317 out.writeInt(STATUS_FILE_ITEM);
1318 status.writeToParcel(out, 0);
1319 }
1320 out.writeInt(STATUS_FILE_END);
1321 fos.write(out.marshall());
1322 out.recycle();
1323
1324 mStatusFile.finishWrite(fos);
1325 } catch (java.io.IOException e1) {
1326 Log.w(TAG, "Error writing status", e1);
1327 if (fos != null) {
1328 mStatusFile.failWrite(fos);
1329 }
1330 }
1331 }
1332
1333 public static final int PENDING_OPERATION_VERSION = 1;
1334
1335 /**
1336 * Read all pending operations back in to the initial engine state.
1337 */
1338 private void readPendingOperationsLocked() {
1339 if (DEBUG_FILE) Log.v(TAG, "Reading " + mPendingFile.getBaseFile());
1340 try {
1341 byte[] data = mPendingFile.readFully();
1342 Parcel in = Parcel.obtain();
1343 in.unmarshall(data, 0, data.length);
1344 in.setDataPosition(0);
1345 final int SIZE = in.dataSize();
1346 while (in.dataPosition() < SIZE) {
1347 int version = in.readInt();
1348 if (version != PENDING_OPERATION_VERSION) {
1349 Log.w(TAG, "Unknown pending operation version "
1350 + version + "; dropping all ops");
1351 break;
1352 }
1353 int authorityId = in.readInt();
1354 int syncSource = in.readInt();
1355 byte[] flatExtras = in.createByteArray();
1356 AuthorityInfo authority = mAuthorities.get(authorityId);
1357 if (authority != null) {
1358 Bundle extras = null;
1359 if (flatExtras != null) {
1360 extras = unflattenBundle(flatExtras);
1361 }
1362 PendingOperation op = new PendingOperation(
1363 authority.account, syncSource,
1364 authority.authority, extras);
1365 op.authorityId = authorityId;
1366 op.flatExtras = flatExtras;
1367 if (DEBUG_FILE) Log.v(TAG, "Adding pending op: account=" + op.account
1368 + " auth=" + op.authority
1369 + " src=" + op.syncSource
1370 + " extras=" + op.extras);
1371 mPendingOperations.add(op);
1372 }
1373 }
1374 } catch (java.io.IOException e) {
1375 Log.i(TAG, "No initial pending operations");
1376 }
1377 }
1378
1379 private void writePendingOperationLocked(PendingOperation op, Parcel out) {
1380 out.writeInt(PENDING_OPERATION_VERSION);
1381 out.writeInt(op.authorityId);
1382 out.writeInt(op.syncSource);
1383 if (op.flatExtras == null && op.extras != null) {
1384 op.flatExtras = flattenBundle(op.extras);
1385 }
1386 out.writeByteArray(op.flatExtras);
1387 }
1388
1389 /**
1390 * Write all currently pending ops to the pending ops file.
1391 */
1392 private void writePendingOperationsLocked() {
1393 final int N = mPendingOperations.size();
1394 FileOutputStream fos = null;
1395 try {
1396 if (N == 0) {
1397 if (DEBUG_FILE) Log.v(TAG, "Truncating " + mPendingFile.getBaseFile());
1398 mPendingFile.truncate();
1399 return;
1400 }
1401
1402 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mPendingFile.getBaseFile());
1403 fos = mPendingFile.startWrite();
1404
1405 Parcel out = Parcel.obtain();
1406 for (int i=0; i<N; i++) {
1407 PendingOperation op = mPendingOperations.get(i);
1408 writePendingOperationLocked(op, out);
1409 }
1410 fos.write(out.marshall());
1411 out.recycle();
1412
1413 mPendingFile.finishWrite(fos);
1414 } catch (java.io.IOException e1) {
1415 Log.w(TAG, "Error writing pending operations", e1);
1416 if (fos != null) {
1417 mPendingFile.failWrite(fos);
1418 }
1419 }
1420 }
1421
1422 /**
1423 * Append the given operation to the pending ops file; if unable to,
1424 * write all pending ops.
1425 */
1426 private void appendPendingOperationLocked(PendingOperation op) {
1427 if (DEBUG_FILE) Log.v(TAG, "Appending to " + mPendingFile.getBaseFile());
1428 FileOutputStream fos = null;
1429 try {
1430 fos = mPendingFile.openAppend();
1431 } catch (java.io.IOException e) {
1432 if (DEBUG_FILE) Log.v(TAG, "Failed append; writing full file");
1433 writePendingOperationsLocked();
1434 return;
1435 }
1436
1437 try {
1438 Parcel out = Parcel.obtain();
1439 writePendingOperationLocked(op, out);
1440 fos.write(out.marshall());
1441 out.recycle();
1442 } catch (java.io.IOException e1) {
1443 Log.w(TAG, "Error writing pending operations", e1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001444 } finally {
Dianne Hackborn231cc602009-04-27 17:10:36 -07001445 try {
1446 fos.close();
1447 } catch (java.io.IOException e2) {
1448 }
1449 }
1450 }
1451
1452 static private byte[] flattenBundle(Bundle bundle) {
1453 byte[] flatData = null;
1454 Parcel parcel = Parcel.obtain();
1455 try {
1456 bundle.writeToParcel(parcel, 0);
1457 flatData = parcel.marshall();
1458 } finally {
1459 parcel.recycle();
1460 }
1461 return flatData;
1462 }
1463
1464 static private Bundle unflattenBundle(byte[] flatData) {
1465 Bundle bundle;
1466 Parcel parcel = Parcel.obtain();
1467 try {
1468 parcel.unmarshall(flatData, 0, flatData.length);
1469 parcel.setDataPosition(0);
1470 bundle = parcel.readBundle();
1471 } catch (RuntimeException e) {
1472 // A RuntimeException is thrown if we were unable to parse the parcel.
1473 // Create an empty parcel in this case.
1474 bundle = new Bundle();
1475 } finally {
1476 parcel.recycle();
1477 }
1478 return bundle;
1479 }
1480
1481 public static final int STATISTICS_FILE_END = 0;
1482 public static final int STATISTICS_FILE_ITEM_OLD = 100;
1483 public static final int STATISTICS_FILE_ITEM = 101;
1484
1485 /**
1486 * Read all sync statistics back in to the initial engine state.
1487 */
1488 private void readStatisticsLocked() {
1489 try {
1490 byte[] data = mStatisticsFile.readFully();
1491 Parcel in = Parcel.obtain();
1492 in.unmarshall(data, 0, data.length);
1493 in.setDataPosition(0);
1494 int token;
1495 int index = 0;
1496 while ((token=in.readInt()) != STATISTICS_FILE_END) {
1497 if (token == STATISTICS_FILE_ITEM
1498 || token == STATISTICS_FILE_ITEM_OLD) {
1499 int day = in.readInt();
1500 if (token == STATISTICS_FILE_ITEM_OLD) {
1501 day = day - 2009 + 14245; // Magic!
1502 }
1503 DayStats ds = new DayStats(day);
1504 ds.successCount = in.readInt();
1505 ds.successTime = in.readLong();
1506 ds.failureCount = in.readInt();
1507 ds.failureTime = in.readLong();
1508 if (index < mDayStats.length) {
1509 mDayStats[index] = ds;
1510 index++;
1511 }
1512 } else {
1513 // Ooops.
1514 Log.w(TAG, "Unknown stats token: " + token);
1515 break;
1516 }
1517 }
1518 } catch (java.io.IOException e) {
1519 Log.i(TAG, "No initial statistics");
1520 }
1521 }
1522
1523 /**
1524 * Write all sync statistics to the sync status file.
1525 */
1526 private void writeStatisticsLocked() {
1527 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatisticsFile.getBaseFile());
1528
1529 // The file is being written, so we don't need to have a scheduled
1530 // write until the next change.
1531 removeMessages(MSG_WRITE_STATISTICS);
1532
1533 FileOutputStream fos = null;
1534 try {
1535 fos = mStatisticsFile.startWrite();
1536 Parcel out = Parcel.obtain();
1537 final int N = mDayStats.length;
1538 for (int i=0; i<N; i++) {
1539 DayStats ds = mDayStats[i];
1540 if (ds == null) {
1541 break;
1542 }
1543 out.writeInt(STATISTICS_FILE_ITEM);
1544 out.writeInt(ds.day);
1545 out.writeInt(ds.successCount);
1546 out.writeLong(ds.successTime);
1547 out.writeInt(ds.failureCount);
1548 out.writeLong(ds.failureTime);
1549 }
1550 out.writeInt(STATISTICS_FILE_END);
1551 fos.write(out.marshall());
1552 out.recycle();
1553
1554 mStatisticsFile.finishWrite(fos);
1555 } catch (java.io.IOException e1) {
1556 Log.w(TAG, "Error writing stats", e1);
1557 if (fos != null) {
1558 mStatisticsFile.failWrite(fos);
1559 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001560 }
1561 }
1562}