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