blob: 38278496411a4b00d3e2703ec6df435ddf71e272 [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;
Dianne Hackborn55280a92009-05-07 15:53:46 -0700739 int day = getCurrentDayLocked();
Dianne Hackborn231cc602009-04-27 17:10:36 -0700740 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
Dianne Hackborn55280a92009-05-07 15:53:46 -0700928 private int getCurrentDayLocked() {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700929 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
Dianne Hackborn55280a92009-05-07 15:53:46 -07001008 public void writeAllState() {
1009 synchronized (mAuthorities) {
1010 // Account info is always written so no need to do it here.
1011
1012 if (mNumPendingFinished > 0) {
1013 // Only write these if they are out of date.
1014 writePendingOperationsLocked();
1015 }
1016
1017 // Just always write these... they are likely out of date.
1018 writeStatusLocked();
1019 writeStatisticsLocked();
1020 }
1021 }
1022
Dianne Hackborn231cc602009-04-27 17:10:36 -07001023 /**
1024 * Read all account information back in to the initial engine state.
1025 */
1026 private void readAccountInfoLocked() {
1027 FileInputStream fis = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001028 try {
Dianne Hackborn231cc602009-04-27 17:10:36 -07001029 fis = mAccountInfoFile.openRead();
1030 if (DEBUG_FILE) Log.v(TAG, "Reading " + mAccountInfoFile.getBaseFile());
1031 XmlPullParser parser = Xml.newPullParser();
1032 parser.setInput(fis, null);
1033 int eventType = parser.getEventType();
1034 while (eventType != XmlPullParser.START_TAG) {
1035 eventType = parser.next();
1036 }
1037 String tagName = parser.getName();
1038 if ("accounts".equals(tagName)) {
1039 String listen = parser.getAttributeValue(
1040 null, "listen-for-tickles");
1041 mListenForTickles = listen == null
1042 || Boolean.parseBoolean(listen);
1043 eventType = parser.next();
1044 do {
1045 if (eventType == XmlPullParser.START_TAG
1046 && parser.getDepth() == 2) {
1047 tagName = parser.getName();
1048 if ("authority".equals(tagName)) {
1049 int id = -1;
1050 try {
1051 id = Integer.parseInt(parser.getAttributeValue(
1052 null, "id"));
1053 } catch (NumberFormatException e) {
1054 } catch (NullPointerException e) {
1055 }
1056 if (id >= 0) {
1057 String accountName = parser.getAttributeValue(
1058 null, "account");
1059 String authorityName = parser.getAttributeValue(
1060 null, "authority");
1061 String enabled = parser.getAttributeValue(
1062 null, "enabled");
1063 AuthorityInfo authority = mAuthorities.get(id);
1064 if (DEBUG_FILE) Log.v(TAG, "Adding authority: account="
1065 + accountName + " auth=" + authorityName
1066 + " enabled=" + enabled);
1067 if (authority == null) {
1068 if (DEBUG_FILE) Log.v(TAG, "Creating entry");
1069 authority = getOrCreateAuthorityLocked(
1070 accountName, authorityName, id, false);
1071 }
1072 if (authority != null) {
1073 authority.enabled = enabled == null
1074 || Boolean.parseBoolean(enabled);
1075 } else {
1076 Log.w(TAG, "Failure adding authority: account="
1077 + accountName + " auth=" + authorityName
1078 + " enabled=" + enabled);
1079 }
1080 }
1081 }
1082 }
1083 eventType = parser.next();
1084 } while (eventType != XmlPullParser.END_DOCUMENT);
1085 }
1086 } catch (XmlPullParserException e) {
1087 Log.w(TAG, "Error reading accounts", e);
1088 } catch (java.io.IOException e) {
1089 if (fis == null) Log.i(TAG, "No initial accounts");
1090 else Log.w(TAG, "Error reading accounts", e);
1091 } finally {
1092 if (fis != null) {
1093 try {
1094 fis.close();
1095 } catch (java.io.IOException e1) {
1096 }
1097 }
1098 }
1099 }
1100
1101 /**
1102 * Write all account information to the account file.
1103 */
1104 private void writeAccountInfoLocked() {
1105 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mAccountInfoFile.getBaseFile());
1106 FileOutputStream fos = null;
1107
1108 try {
1109 fos = mAccountInfoFile.startWrite();
1110 XmlSerializer out = new FastXmlSerializer();
1111 out.setOutput(fos, "utf-8");
1112 out.startDocument(null, true);
1113 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
1114
1115 out.startTag(null, "accounts");
1116 if (!mListenForTickles) {
1117 out.attribute(null, "listen-for-tickles", "false");
1118 }
1119
1120 final int N = mAuthorities.size();
1121 for (int i=0; i<N; i++) {
1122 AuthorityInfo authority = mAuthorities.get(i);
1123 out.startTag(null, "authority");
1124 out.attribute(null, "id", Integer.toString(authority.ident));
1125 out.attribute(null, "account", authority.account);
1126 out.attribute(null, "authority", authority.authority);
1127 if (!authority.enabled) {
1128 out.attribute(null, "enabled", "false");
1129 }
1130 out.endTag(null, "authority");
1131 }
1132
1133 out.endTag(null, "accounts");
1134
1135 out.endDocument();
1136
1137 mAccountInfoFile.finishWrite(fos);
1138 } catch (java.io.IOException e1) {
1139 Log.w(TAG, "Error writing accounts", e1);
1140 if (fos != null) {
1141 mAccountInfoFile.failWrite(fos);
1142 }
1143 }
1144 }
1145
1146 static int getIntColumn(Cursor c, String name) {
1147 return c.getInt(c.getColumnIndex(name));
1148 }
1149
1150 static long getLongColumn(Cursor c, String name) {
1151 return c.getLong(c.getColumnIndex(name));
1152 }
1153
1154 /**
1155 * Load sync engine state from the old syncmanager database, and then
1156 * erase it. Note that we don't deal with pending operations, active
1157 * sync, or history.
1158 */
1159 private void readLegacyAccountInfoLocked() {
1160 // Look for old database to initialize from.
1161 File file = mContext.getDatabasePath("syncmanager.db");
1162 if (!file.exists()) {
1163 return;
1164 }
1165 String path = file.getPath();
1166 SQLiteDatabase db = null;
1167 try {
1168 db = SQLiteDatabase.openDatabase(path, null,
1169 SQLiteDatabase.OPEN_READONLY);
1170 } catch (SQLiteException e) {
1171 }
1172
1173 if (db != null) {
1174 // Copy in all of the status information, as well as accounts.
1175 if (DEBUG_FILE) Log.v(TAG, "Reading legacy sync accounts db");
1176 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
1177 qb.setTables("stats, status");
1178 HashMap<String,String> map = new HashMap<String,String>();
1179 map.put("_id", "status._id as _id");
1180 map.put("account", "stats.account as account");
1181 map.put("authority", "stats.authority as authority");
1182 map.put("totalElapsedTime", "totalElapsedTime");
1183 map.put("numSyncs", "numSyncs");
1184 map.put("numSourceLocal", "numSourceLocal");
1185 map.put("numSourcePoll", "numSourcePoll");
1186 map.put("numSourceServer", "numSourceServer");
1187 map.put("numSourceUser", "numSourceUser");
1188 map.put("lastSuccessSource", "lastSuccessSource");
1189 map.put("lastSuccessTime", "lastSuccessTime");
1190 map.put("lastFailureSource", "lastFailureSource");
1191 map.put("lastFailureTime", "lastFailureTime");
1192 map.put("lastFailureMesg", "lastFailureMesg");
1193 map.put("pending", "pending");
1194 qb.setProjectionMap(map);
1195 qb.appendWhere("stats._id = status.stats_id");
1196 Cursor c = qb.query(db, null, null, null, null, null, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001197 while (c.moveToNext()) {
Dianne Hackborn231cc602009-04-27 17:10:36 -07001198 String accountName = c.getString(c.getColumnIndex("account"));
1199 String authorityName = c.getString(c.getColumnIndex("authority"));
1200 AuthorityInfo authority = this.getOrCreateAuthorityLocked(
1201 accountName, authorityName, -1, false);
1202 if (authority != null) {
1203 int i = mSyncStatus.size();
1204 boolean found = false;
1205 SyncStatusInfo st = null;
1206 while (i > 0) {
1207 i--;
1208 st = mSyncStatus.get(i);
1209 if (st.authorityId == authority.ident) {
1210 found = true;
1211 break;
1212 }
1213 }
1214 if (!found) {
1215 st = new SyncStatusInfo(authority.ident);
1216 mSyncStatus.put(authority.ident, st);
1217 }
1218 st.totalElapsedTime = getLongColumn(c, "totalElapsedTime");
1219 st.numSyncs = getIntColumn(c, "numSyncs");
1220 st.numSourceLocal = getIntColumn(c, "numSourceLocal");
1221 st.numSourcePoll = getIntColumn(c, "numSourcePoll");
1222 st.numSourceServer = getIntColumn(c, "numSourceServer");
1223 st.numSourceUser = getIntColumn(c, "numSourceUser");
1224 st.lastSuccessSource = getIntColumn(c, "lastSuccessSource");
1225 st.lastSuccessTime = getLongColumn(c, "lastSuccessTime");
1226 st.lastFailureSource = getIntColumn(c, "lastFailureSource");
1227 st.lastFailureTime = getLongColumn(c, "lastFailureTime");
1228 st.lastFailureMesg = c.getString(c.getColumnIndex("lastFailureMesg"));
1229 st.pending = getIntColumn(c, "pending") != 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001230 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001231 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001232
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001233 c.close();
Dianne Hackborn231cc602009-04-27 17:10:36 -07001234
1235 // Retrieve the settings.
1236 qb = new SQLiteQueryBuilder();
1237 qb.setTables("settings");
1238 c = qb.query(db, null, null, null, null, null, null);
1239 while (c.moveToNext()) {
1240 String name = c.getString(c.getColumnIndex("name"));
1241 String value = c.getString(c.getColumnIndex("value"));
1242 if (name == null) continue;
1243 if (name.equals("listen_for_tickles")) {
1244 setListenForNetworkTickles(value == null
1245 || Boolean.parseBoolean(value));
1246 } else if (name.startsWith("sync_provider_")) {
1247 String provider = name.substring("sync_provider_".length(),
1248 name.length());
1249 setSyncProviderAutomatically(null, provider,
1250 value == null || Boolean.parseBoolean(value));
1251 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001252 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001253
1254 c.close();
1255
1256 db.close();
1257
1258 writeAccountInfoLocked();
1259 writeStatusLocked();
1260 (new File(path)).delete();
1261 }
1262 }
1263
1264 public static final int STATUS_FILE_END = 0;
1265 public static final int STATUS_FILE_ITEM = 100;
1266
1267 /**
1268 * Read all sync status back in to the initial engine state.
1269 */
1270 private void readStatusLocked() {
1271 if (DEBUG_FILE) Log.v(TAG, "Reading " + mStatusFile.getBaseFile());
1272 try {
1273 byte[] data = mStatusFile.readFully();
1274 Parcel in = Parcel.obtain();
1275 in.unmarshall(data, 0, data.length);
1276 in.setDataPosition(0);
1277 int token;
1278 while ((token=in.readInt()) != STATUS_FILE_END) {
1279 if (token == STATUS_FILE_ITEM) {
1280 SyncStatusInfo status = new SyncStatusInfo(in);
1281 if (mAuthorities.indexOfKey(status.authorityId) >= 0) {
1282 status.pending = false;
1283 if (DEBUG_FILE) Log.v(TAG, "Adding status for id "
1284 + status.authorityId);
1285 mSyncStatus.put(status.authorityId, status);
1286 }
1287 } else {
1288 // Ooops.
1289 Log.w(TAG, "Unknown status token: " + token);
1290 break;
1291 }
1292 }
1293 } catch (java.io.IOException e) {
1294 Log.i(TAG, "No initial status");
1295 }
1296 }
1297
1298 /**
1299 * Write all sync status to the sync status file.
1300 */
1301 private void writeStatusLocked() {
1302 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatusFile.getBaseFile());
1303
1304 // The file is being written, so we don't need to have a scheduled
1305 // write until the next change.
1306 removeMessages(MSG_WRITE_STATUS);
1307
1308 FileOutputStream fos = null;
1309 try {
1310 fos = mStatusFile.startWrite();
1311 Parcel out = Parcel.obtain();
1312 final int N = mSyncStatus.size();
1313 for (int i=0; i<N; i++) {
1314 SyncStatusInfo status = mSyncStatus.valueAt(i);
1315 out.writeInt(STATUS_FILE_ITEM);
1316 status.writeToParcel(out, 0);
1317 }
1318 out.writeInt(STATUS_FILE_END);
1319 fos.write(out.marshall());
1320 out.recycle();
1321
1322 mStatusFile.finishWrite(fos);
1323 } catch (java.io.IOException e1) {
1324 Log.w(TAG, "Error writing status", e1);
1325 if (fos != null) {
1326 mStatusFile.failWrite(fos);
1327 }
1328 }
1329 }
1330
1331 public static final int PENDING_OPERATION_VERSION = 1;
1332
1333 /**
1334 * Read all pending operations back in to the initial engine state.
1335 */
1336 private void readPendingOperationsLocked() {
1337 if (DEBUG_FILE) Log.v(TAG, "Reading " + mPendingFile.getBaseFile());
1338 try {
1339 byte[] data = mPendingFile.readFully();
1340 Parcel in = Parcel.obtain();
1341 in.unmarshall(data, 0, data.length);
1342 in.setDataPosition(0);
1343 final int SIZE = in.dataSize();
1344 while (in.dataPosition() < SIZE) {
1345 int version = in.readInt();
1346 if (version != PENDING_OPERATION_VERSION) {
1347 Log.w(TAG, "Unknown pending operation version "
1348 + version + "; dropping all ops");
1349 break;
1350 }
1351 int authorityId = in.readInt();
1352 int syncSource = in.readInt();
1353 byte[] flatExtras = in.createByteArray();
1354 AuthorityInfo authority = mAuthorities.get(authorityId);
1355 if (authority != null) {
1356 Bundle extras = null;
1357 if (flatExtras != null) {
1358 extras = unflattenBundle(flatExtras);
1359 }
1360 PendingOperation op = new PendingOperation(
1361 authority.account, syncSource,
1362 authority.authority, extras);
1363 op.authorityId = authorityId;
1364 op.flatExtras = flatExtras;
1365 if (DEBUG_FILE) Log.v(TAG, "Adding pending op: account=" + op.account
1366 + " auth=" + op.authority
1367 + " src=" + op.syncSource
1368 + " extras=" + op.extras);
1369 mPendingOperations.add(op);
1370 }
1371 }
1372 } catch (java.io.IOException e) {
1373 Log.i(TAG, "No initial pending operations");
1374 }
1375 }
1376
1377 private void writePendingOperationLocked(PendingOperation op, Parcel out) {
1378 out.writeInt(PENDING_OPERATION_VERSION);
1379 out.writeInt(op.authorityId);
1380 out.writeInt(op.syncSource);
1381 if (op.flatExtras == null && op.extras != null) {
1382 op.flatExtras = flattenBundle(op.extras);
1383 }
1384 out.writeByteArray(op.flatExtras);
1385 }
1386
1387 /**
1388 * Write all currently pending ops to the pending ops file.
1389 */
1390 private void writePendingOperationsLocked() {
1391 final int N = mPendingOperations.size();
1392 FileOutputStream fos = null;
1393 try {
1394 if (N == 0) {
1395 if (DEBUG_FILE) Log.v(TAG, "Truncating " + mPendingFile.getBaseFile());
1396 mPendingFile.truncate();
1397 return;
1398 }
1399
1400 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mPendingFile.getBaseFile());
1401 fos = mPendingFile.startWrite();
1402
1403 Parcel out = Parcel.obtain();
1404 for (int i=0; i<N; i++) {
1405 PendingOperation op = mPendingOperations.get(i);
1406 writePendingOperationLocked(op, out);
1407 }
1408 fos.write(out.marshall());
1409 out.recycle();
1410
1411 mPendingFile.finishWrite(fos);
1412 } catch (java.io.IOException e1) {
1413 Log.w(TAG, "Error writing pending operations", e1);
1414 if (fos != null) {
1415 mPendingFile.failWrite(fos);
1416 }
1417 }
1418 }
1419
1420 /**
1421 * Append the given operation to the pending ops file; if unable to,
1422 * write all pending ops.
1423 */
1424 private void appendPendingOperationLocked(PendingOperation op) {
1425 if (DEBUG_FILE) Log.v(TAG, "Appending to " + mPendingFile.getBaseFile());
1426 FileOutputStream fos = null;
1427 try {
1428 fos = mPendingFile.openAppend();
1429 } catch (java.io.IOException e) {
1430 if (DEBUG_FILE) Log.v(TAG, "Failed append; writing full file");
1431 writePendingOperationsLocked();
1432 return;
1433 }
1434
1435 try {
1436 Parcel out = Parcel.obtain();
1437 writePendingOperationLocked(op, out);
1438 fos.write(out.marshall());
1439 out.recycle();
1440 } catch (java.io.IOException e1) {
1441 Log.w(TAG, "Error writing pending operations", e1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001442 } finally {
Dianne Hackborn231cc602009-04-27 17:10:36 -07001443 try {
1444 fos.close();
1445 } catch (java.io.IOException e2) {
1446 }
1447 }
1448 }
1449
1450 static private byte[] flattenBundle(Bundle bundle) {
1451 byte[] flatData = null;
1452 Parcel parcel = Parcel.obtain();
1453 try {
1454 bundle.writeToParcel(parcel, 0);
1455 flatData = parcel.marshall();
1456 } finally {
1457 parcel.recycle();
1458 }
1459 return flatData;
1460 }
1461
1462 static private Bundle unflattenBundle(byte[] flatData) {
1463 Bundle bundle;
1464 Parcel parcel = Parcel.obtain();
1465 try {
1466 parcel.unmarshall(flatData, 0, flatData.length);
1467 parcel.setDataPosition(0);
1468 bundle = parcel.readBundle();
1469 } catch (RuntimeException e) {
1470 // A RuntimeException is thrown if we were unable to parse the parcel.
1471 // Create an empty parcel in this case.
1472 bundle = new Bundle();
1473 } finally {
1474 parcel.recycle();
1475 }
1476 return bundle;
1477 }
1478
1479 public static final int STATISTICS_FILE_END = 0;
1480 public static final int STATISTICS_FILE_ITEM_OLD = 100;
1481 public static final int STATISTICS_FILE_ITEM = 101;
1482
1483 /**
1484 * Read all sync statistics back in to the initial engine state.
1485 */
1486 private void readStatisticsLocked() {
1487 try {
1488 byte[] data = mStatisticsFile.readFully();
1489 Parcel in = Parcel.obtain();
1490 in.unmarshall(data, 0, data.length);
1491 in.setDataPosition(0);
1492 int token;
1493 int index = 0;
1494 while ((token=in.readInt()) != STATISTICS_FILE_END) {
1495 if (token == STATISTICS_FILE_ITEM
1496 || token == STATISTICS_FILE_ITEM_OLD) {
1497 int day = in.readInt();
1498 if (token == STATISTICS_FILE_ITEM_OLD) {
1499 day = day - 2009 + 14245; // Magic!
1500 }
1501 DayStats ds = new DayStats(day);
1502 ds.successCount = in.readInt();
1503 ds.successTime = in.readLong();
1504 ds.failureCount = in.readInt();
1505 ds.failureTime = in.readLong();
1506 if (index < mDayStats.length) {
1507 mDayStats[index] = ds;
1508 index++;
1509 }
1510 } else {
1511 // Ooops.
1512 Log.w(TAG, "Unknown stats token: " + token);
1513 break;
1514 }
1515 }
1516 } catch (java.io.IOException e) {
1517 Log.i(TAG, "No initial statistics");
1518 }
1519 }
1520
1521 /**
1522 * Write all sync statistics to the sync status file.
1523 */
1524 private void writeStatisticsLocked() {
1525 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatisticsFile.getBaseFile());
1526
1527 // The file is being written, so we don't need to have a scheduled
1528 // write until the next change.
1529 removeMessages(MSG_WRITE_STATISTICS);
1530
1531 FileOutputStream fos = null;
1532 try {
1533 fos = mStatisticsFile.startWrite();
1534 Parcel out = Parcel.obtain();
1535 final int N = mDayStats.length;
1536 for (int i=0; i<N; i++) {
1537 DayStats ds = mDayStats[i];
1538 if (ds == null) {
1539 break;
1540 }
1541 out.writeInt(STATISTICS_FILE_ITEM);
1542 out.writeInt(ds.day);
1543 out.writeInt(ds.successCount);
1544 out.writeLong(ds.successTime);
1545 out.writeInt(ds.failureCount);
1546 out.writeLong(ds.failureTime);
1547 }
1548 out.writeInt(STATISTICS_FILE_END);
1549 fos.write(out.marshall());
1550 out.recycle();
1551
1552 mStatisticsFile.finishWrite(fos);
1553 } catch (java.io.IOException e1) {
1554 Log.w(TAG, "Error writing stats", e1);
1555 if (fos != null) {
1556 mStatisticsFile.failWrite(fos);
1557 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001558 }
1559 }
1560}