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