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