blob: aaba7c7c1891dfcb8b27e4b2ddf22af9c1f571a1 [file] [log] [blame]
Dianne Hackborn231cc602009-04-27 17:10:36 -07001/*
2 * Copyright (C) 2009 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080017package android.content;
18
Dianne Hackborn231cc602009-04-27 17:10:36 -070019import com.android.internal.os.AtomicFile;
20import com.android.internal.util.ArrayUtils;
21import com.android.internal.util.FastXmlSerializer;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022
Dianne Hackborn231cc602009-04-27 17:10:36 -070023import org.xmlpull.v1.XmlPullParser;
24import org.xmlpull.v1.XmlPullParserException;
25import org.xmlpull.v1.XmlSerializer;
26
Fred Quintanad9d2f112009-04-23 13:36:27 -070027import android.accounts.Account;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.database.Cursor;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.database.sqlite.SQLiteDatabase;
Dianne Hackborn231cc602009-04-27 17:10:36 -070030import android.database.sqlite.SQLiteException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.database.sqlite.SQLiteQueryBuilder;
Dianne Hackborn231cc602009-04-27 17:10:36 -070032import android.os.Bundle;
33import android.os.Environment;
34import android.os.Handler;
35import android.os.Message;
36import android.os.Parcel;
37import android.os.RemoteCallbackList;
38import android.os.RemoteException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import android.util.Log;
Dianne Hackborn231cc602009-04-27 17:10:36 -070040import android.util.SparseArray;
41import android.util.Xml;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042
Dianne Hackborn231cc602009-04-27 17:10:36 -070043import java.io.File;
44import java.io.FileInputStream;
45import java.io.FileOutputStream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046import java.util.ArrayList;
Dianne Hackborn231cc602009-04-27 17:10:36 -070047import java.util.Calendar;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048import java.util.HashMap;
Dianne Hackborn231cc602009-04-27 17:10:36 -070049import java.util.Iterator;
50import java.util.TimeZone;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080051
52/**
Dianne Hackborn231cc602009-04-27 17:10:36 -070053 * Singleton that tracks the sync data and overall sync
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080054 * history on the device.
55 *
56 * @hide
57 */
Dianne Hackborn231cc602009-04-27 17:10:36 -070058public class SyncStorageEngine extends Handler {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080059 private static final String TAG = "SyncManager";
Dianne Hackborn231cc602009-04-27 17:10:36 -070060 private static final boolean DEBUG = false;
61 private static final boolean DEBUG_FILE = false;
62
63 // @VisibleForTesting
64 static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065
Dianne Hackborn231cc602009-04-27 17:10:36 -070066 /** Enum value for a sync start event. */
67 public static final int EVENT_START = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080068
Dianne Hackborn231cc602009-04-27 17:10:36 -070069 /** Enum value for a sync stop event. */
70 public static final int EVENT_STOP = 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080071
Dianne Hackborn231cc602009-04-27 17:10:36 -070072 // TODO: i18n -- grab these out of resources.
73 /** String names for the sync event types. */
74 public static final String[] EVENTS = { "START", "STOP" };
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080075
Dianne Hackborn231cc602009-04-27 17:10:36 -070076 /** Enum value for a server-initiated sync. */
77 public static final int SOURCE_SERVER = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080078
Dianne Hackborn231cc602009-04-27 17:10:36 -070079 /** Enum value for a local-initiated sync. */
80 public static final int SOURCE_LOCAL = 1;
81 /**
82 * Enum value for a poll-based sync (e.g., upon connection to
83 * network)
84 */
85 public static final int SOURCE_POLL = 2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080086
Dianne Hackborn231cc602009-04-27 17:10:36 -070087 /** Enum value for a user-initiated sync. */
88 public static final int SOURCE_USER = 3;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080089
Fred Quintanaac9385e2009-06-22 18:00:59 -070090 private static final Intent SYNC_CONNECTION_SETTING_CHANGED_INTENT =
91 new Intent("com.android.sync.SYNC_CONN_STATUS_CHANGED");
92
Dianne Hackborn231cc602009-04-27 17:10:36 -070093 // TODO: i18n -- grab these out of resources.
94 /** String names for the sync source types. */
95 public static final String[] SOURCES = { "SERVER",
96 "LOCAL",
97 "POLL",
98 "USER" };
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080099
Dianne Hackborn231cc602009-04-27 17:10:36 -0700100 // The MESG column will contain one of these or one of the Error types.
101 public static final String MESG_SUCCESS = "success";
102 public static final String MESG_CANCELED = "canceled";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800103
Dianne Hackborn231cc602009-04-27 17:10:36 -0700104 public static final int MAX_HISTORY = 15;
105
106 private static final int MSG_WRITE_STATUS = 1;
107 private static final long WRITE_STATUS_DELAY = 1000*60*10; // 10 minutes
108
109 private static final int MSG_WRITE_STATISTICS = 2;
110 private static final long WRITE_STATISTICS_DELAY = 1000*60*30; // 1/2 hour
111
112 public static class PendingOperation {
Dianne Hackborn7a135592009-05-06 00:28:37 -0700113 final Account account;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700114 final int syncSource;
115 final String authority;
116 final Bundle extras; // note: read-only.
117
118 int authorityId;
119 byte[] flatExtras;
120
Dianne Hackborn7a135592009-05-06 00:28:37 -0700121 PendingOperation(Account account, int source,
Dianne Hackborn231cc602009-04-27 17:10:36 -0700122 String authority, Bundle extras) {
123 this.account = account;
124 this.syncSource = source;
125 this.authority = authority;
126 this.extras = extras != null ? new Bundle(extras) : extras;
127 this.authorityId = -1;
128 }
129
130 PendingOperation(PendingOperation other) {
131 this.account = other.account;
132 this.syncSource = other.syncSource;
133 this.authority = other.authority;
134 this.extras = other.extras;
135 this.authorityId = other.authorityId;
136 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800137 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700138
139 static class AccountInfo {
Dianne Hackborn7a135592009-05-06 00:28:37 -0700140 final Account account;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700141 final HashMap<String, AuthorityInfo> authorities =
142 new HashMap<String, AuthorityInfo>();
143
Dianne Hackborn7a135592009-05-06 00:28:37 -0700144 AccountInfo(Account account) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700145 this.account = account;
146 }
147 }
148
149 public static class AuthorityInfo {
Dianne Hackborn7a135592009-05-06 00:28:37 -0700150 final Account account;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700151 final String authority;
152 final int ident;
153 boolean enabled;
Fred Quintanaac9385e2009-06-22 18:00:59 -0700154
Dianne Hackborn7a135592009-05-06 00:28:37 -0700155 AuthorityInfo(Account account, String authority, int ident) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700156 this.account = account;
157 this.authority = authority;
158 this.ident = ident;
159 enabled = true;
160 }
161 }
162
163 public static class SyncHistoryItem {
164 int authorityId;
165 int historyId;
166 long eventTime;
167 long elapsedTime;
168 int source;
169 int event;
170 long upstreamActivity;
171 long downstreamActivity;
172 String mesg;
173 }
174
175 public static class DayStats {
176 public final int day;
177 public int successCount;
178 public long successTime;
179 public int failureCount;
180 public long failureTime;
181
182 public DayStats(int day) {
183 this.day = day;
184 }
185 }
186
187 // Primary list of all syncable authorities. Also our global lock.
188 private final SparseArray<AuthorityInfo> mAuthorities =
189 new SparseArray<AuthorityInfo>();
190
Dianne Hackborn7a135592009-05-06 00:28:37 -0700191 private final HashMap<Account, AccountInfo> mAccounts =
192 new HashMap<Account, AccountInfo>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800193
Dianne Hackborn231cc602009-04-27 17:10:36 -0700194 private final ArrayList<PendingOperation> mPendingOperations =
195 new ArrayList<PendingOperation>();
196
197 private ActiveSyncInfo mActiveSync;
198
199 private final SparseArray<SyncStatusInfo> mSyncStatus =
200 new SparseArray<SyncStatusInfo>();
201
202 private final ArrayList<SyncHistoryItem> mSyncHistory =
203 new ArrayList<SyncHistoryItem>();
204
205 private final RemoteCallbackList<ISyncStatusObserver> mChangeListeners
206 = new RemoteCallbackList<ISyncStatusObserver>();
207
208 // We keep 4 weeks of stats.
209 private final DayStats[] mDayStats = new DayStats[7*4];
210 private final Calendar mCal;
211 private int mYear;
212 private int mYearInDays;
213
214 private final Context mContext;
215 private static volatile SyncStorageEngine sSyncStorageEngine = null;
216
217 /**
218 * This file contains the core engine state: all accounts and the
219 * settings for them. It must never be lost, and should be changed
220 * infrequently, so it is stored as an XML file.
221 */
222 private final AtomicFile mAccountInfoFile;
223
224 /**
225 * This file contains the current sync status. We would like to retain
226 * it across boots, but its loss is not the end of the world, so we store
227 * this information as binary data.
228 */
229 private final AtomicFile mStatusFile;
230
231 /**
232 * This file contains sync statistics. This is purely debugging information
233 * so is written infrequently and can be thrown away at any time.
234 */
235 private final AtomicFile mStatisticsFile;
236
237 /**
238 * This file contains the pending sync operations. It is a binary file,
239 * which must be updated every time an operation is added or removed,
240 * so we have special handling of it.
241 */
242 private final AtomicFile mPendingFile;
243 private static final int PENDING_FINISH_TO_WRITE = 4;
244 private int mNumPendingFinished = 0;
245
246 private int mNextHistoryId = 0;
Fred Quintanaac9385e2009-06-22 18:00:59 -0700247 private boolean mMasterSyncAutomatically = true;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700248
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800249 private SyncStorageEngine(Context context) {
250 mContext = context;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800251 sSyncStorageEngine = this;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700252
253 mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
254
255 File dataDir = Environment.getDataDirectory();
256 File systemDir = new File(dataDir, "system");
257 File syncDir = new File(systemDir, "sync");
258 mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
259 mStatusFile = new AtomicFile(new File(syncDir, "status.bin"));
260 mPendingFile = new AtomicFile(new File(syncDir, "pending.bin"));
261 mStatisticsFile = new AtomicFile(new File(syncDir, "stats.bin"));
262
263 readAccountInfoLocked();
264 readStatusLocked();
265 readPendingOperationsLocked();
266 readStatisticsLocked();
267 readLegacyAccountInfoLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800268 }
269
270 public static SyncStorageEngine newTestInstance(Context context) {
271 return new SyncStorageEngine(context);
272 }
273
274 public static void init(Context context) {
275 if (sSyncStorageEngine != null) {
276 throw new IllegalStateException("already initialized");
277 }
278 sSyncStorageEngine = new SyncStorageEngine(context);
279 }
280
281 public static SyncStorageEngine getSingleton() {
282 if (sSyncStorageEngine == null) {
283 throw new IllegalStateException("not initialized");
284 }
285 return sSyncStorageEngine;
286 }
287
Dianne Hackborn231cc602009-04-27 17:10:36 -0700288 @Override public void handleMessage(Message msg) {
289 if (msg.what == MSG_WRITE_STATUS) {
290 synchronized (mAccounts) {
291 writeStatusLocked();
Fred Quintanad9d2f112009-04-23 13:36:27 -0700292 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700293 } else if (msg.what == MSG_WRITE_STATISTICS) {
294 synchronized (mAccounts) {
295 writeStatisticsLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800296 }
297 }
298 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700299
300 public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
301 synchronized (mAuthorities) {
302 mChangeListeners.register(callback, mask);
303 }
304 }
305
306 public void removeStatusChangeListener(ISyncStatusObserver callback) {
307 synchronized (mAuthorities) {
308 mChangeListeners.unregister(callback);
309 }
310 }
311
312 private void reportChange(int which) {
313 ArrayList<ISyncStatusObserver> reports = null;
314 synchronized (mAuthorities) {
315 int i = mChangeListeners.beginBroadcast();
316 while (i > 0) {
317 i--;
318 Integer mask = (Integer)mChangeListeners.getBroadcastCookie(i);
319 if ((which & mask.intValue()) == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800320 continue;
321 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700322 if (reports == null) {
323 reports = new ArrayList<ISyncStatusObserver>(i);
324 }
325 reports.add(mChangeListeners.getBroadcastItem(i));
326 }
327 }
328
329 if (DEBUG) Log.v(TAG, "reportChange " + which + " to: " + reports);
330
331 if (reports != null) {
332 int i = reports.size();
333 while (i > 0) {
334 i--;
335 try {
336 reports.get(i).onStatusChanged(which);
337 } catch (RemoteException e) {
338 // The remote callback list will take care of this for us.
339 }
340 }
341 }
342 }
343
Fred Quintanaac9385e2009-06-22 18:00:59 -0700344 public boolean getSyncAutomatically(Account account, String providerName) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700345 synchronized (mAuthorities) {
346 if (account != null) {
347 AuthorityInfo authority = getAuthorityLocked(account, providerName,
Fred Quintanaac9385e2009-06-22 18:00:59 -0700348 "getSyncAutomatically");
349 return authority != null && authority.enabled;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700350 }
Fred Quintanaac9385e2009-06-22 18:00:59 -0700351
Dianne Hackborn231cc602009-04-27 17:10:36 -0700352 int i = mAuthorities.size();
353 while (i > 0) {
354 i--;
355 AuthorityInfo authority = mAuthorities.get(i);
356 if (authority.authority.equals(providerName)
357 && authority.enabled) {
358 return true;
359 }
360 }
361 return false;
362 }
363 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800364
Fred Quintanaac9385e2009-06-22 18:00:59 -0700365 public void setSyncAutomatically(Account account, String providerName, boolean sync) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700366 synchronized (mAuthorities) {
Fred Quintanaac9385e2009-06-22 18:00:59 -0700367 AuthorityInfo authority = getAuthorityLocked(account, providerName,
368 "setSyncAutomatically");
369 if (authority != null) {
370 authority.enabled = sync;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800371 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700372 writeAccountInfoLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800373 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800374
Fred Quintanaac9385e2009-06-22 18:00:59 -0700375 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800376 }
377
Fred Quintanaac9385e2009-06-22 18:00:59 -0700378 public void setMasterSyncAutomatically(boolean flag) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700379 synchronized (mAuthorities) {
Fred Quintanaac9385e2009-06-22 18:00:59 -0700380 mMasterSyncAutomatically = flag;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700381 writeAccountInfoLocked();
382 }
Fred Quintanaac9385e2009-06-22 18:00:59 -0700383 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
384 mContext.sendBroadcast(SYNC_CONNECTION_SETTING_CHANGED_INTENT);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700385 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800386
Fred Quintanaac9385e2009-06-22 18:00:59 -0700387 public boolean getMasterSyncAutomatically() {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700388 synchronized (mAuthorities) {
Fred Quintanaac9385e2009-06-22 18:00:59 -0700389 return mMasterSyncAutomatically;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700390 }
391 }
392
Dianne Hackborn7a135592009-05-06 00:28:37 -0700393 public AuthorityInfo getAuthority(Account account, String authority) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700394 synchronized (mAuthorities) {
395 return getAuthorityLocked(account, authority, null);
396 }
397 }
398
399 public AuthorityInfo getAuthority(int authorityId) {
400 synchronized (mAuthorities) {
401 return mAuthorities.get(authorityId);
402 }
403 }
404
405 /**
406 * Returns true if there is currently a sync operation for the given
407 * account or authority in the pending list, or actively being processed.
408 */
Dianne Hackborn7a135592009-05-06 00:28:37 -0700409 public boolean isSyncActive(Account account, String authority) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700410 synchronized (mAuthorities) {
411 int i = mPendingOperations.size();
412 while (i > 0) {
413 i--;
414 // TODO(fredq): this probably shouldn't be considering
415 // pending operations.
416 PendingOperation op = mPendingOperations.get(i);
417 if (op.account.equals(account) && op.authority.equals(authority)) {
418 return true;
419 }
420 }
421
422 if (mActiveSync != null) {
423 AuthorityInfo ainfo = getAuthority(mActiveSync.authorityId);
424 if (ainfo != null && ainfo.account.equals(account)
425 && ainfo.authority.equals(authority)) {
426 return true;
427 }
428 }
429 }
430
431 return false;
432 }
433
434 public PendingOperation insertIntoPending(PendingOperation op) {
435 synchronized (mAuthorities) {
436 if (DEBUG) Log.v(TAG, "insertIntoPending: account=" + op.account
437 + " auth=" + op.authority
438 + " src=" + op.syncSource
439 + " extras=" + op.extras);
440
441 AuthorityInfo authority = getOrCreateAuthorityLocked(op.account,
442 op.authority,
443 -1 /* desired identifier */,
444 true /* write accounts to storage */);
445 if (authority == null) {
446 return null;
447 }
448
449 op = new PendingOperation(op);
450 op.authorityId = authority.ident;
451 mPendingOperations.add(op);
452 appendPendingOperationLocked(op);
453
454 SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
455 status.pending = true;
456 }
457
Fred Quintanaac9385e2009-06-22 18:00:59 -0700458 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700459 return op;
460 }
461
462 public boolean deleteFromPending(PendingOperation op) {
463 boolean res = false;
464 synchronized (mAuthorities) {
465 if (DEBUG) Log.v(TAG, "deleteFromPending: account=" + op.account
466 + " auth=" + op.authority
467 + " src=" + op.syncSource
468 + " extras=" + op.extras);
469 if (mPendingOperations.remove(op)) {
470 if (mPendingOperations.size() == 0
471 || mNumPendingFinished >= PENDING_FINISH_TO_WRITE) {
472 writePendingOperationsLocked();
473 mNumPendingFinished = 0;
474 } else {
475 mNumPendingFinished++;
476 }
477
478 AuthorityInfo authority = getAuthorityLocked(op.account, op.authority,
479 "deleteFromPending");
480 if (authority != null) {
481 if (DEBUG) Log.v(TAG, "removing - " + authority);
482 final int N = mPendingOperations.size();
483 boolean morePending = false;
484 for (int i=0; i<N; i++) {
485 PendingOperation cur = mPendingOperations.get(i);
486 if (cur.account.equals(op.account)
487 && cur.authority.equals(op.authority)) {
488 morePending = true;
489 break;
490 }
491 }
492
493 if (!morePending) {
494 if (DEBUG) Log.v(TAG, "no more pending!");
495 SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
496 status.pending = false;
497 }
498 }
499
500 res = true;
501 }
502 }
503
Fred Quintanaac9385e2009-06-22 18:00:59 -0700504 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700505 return res;
506 }
507
508 public int clearPending() {
509 int num;
510 synchronized (mAuthorities) {
511 if (DEBUG) Log.v(TAG, "clearPending");
512 num = mPendingOperations.size();
513 mPendingOperations.clear();
514 final int N = mSyncStatus.size();
515 for (int i=0; i<N; i++) {
516 mSyncStatus.get(i).pending = false;
517 }
518 writePendingOperationsLocked();
519 }
Fred Quintanaac9385e2009-06-22 18:00:59 -0700520 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700521 return num;
522 }
523
524 /**
525 * Return a copy of the current array of pending operations. The
526 * PendingOperation objects are the real objects stored inside, so that
527 * they can be used with deleteFromPending().
528 */
529 public ArrayList<PendingOperation> getPendingOperations() {
530 synchronized (mAuthorities) {
531 return new ArrayList<PendingOperation>(mPendingOperations);
532 }
533 }
534
535 /**
536 * Return the number of currently pending operations.
537 */
538 public int getPendingOperationCount() {
539 synchronized (mAuthorities) {
540 return mPendingOperations.size();
541 }
542 }
543
544 /**
545 * Called when the set of account has changed, given the new array of
546 * active accounts.
547 */
Dianne Hackborn7a135592009-05-06 00:28:37 -0700548 public void doDatabaseCleanup(Account[] accounts) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700549 synchronized (mAuthorities) {
550 if (DEBUG) Log.w(TAG, "Updating for new accounts...");
551 SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>();
552 Iterator<AccountInfo> accIt = mAccounts.values().iterator();
553 while (accIt.hasNext()) {
554 AccountInfo acc = accIt.next();
555 if (!ArrayUtils.contains(accounts, acc.account)) {
556 // This account no longer exists...
557 if (DEBUG) Log.w(TAG, "Account removed: " + acc.account);
558 for (AuthorityInfo auth : acc.authorities.values()) {
559 removing.put(auth.ident, auth);
560 }
561 accIt.remove();
562 }
563 }
564
565 // Clean out all data structures.
566 int i = removing.size();
567 if (i > 0) {
568 while (i > 0) {
569 i--;
570 int ident = removing.keyAt(i);
571 mAuthorities.remove(ident);
572 int j = mSyncStatus.size();
573 while (j > 0) {
574 j--;
575 if (mSyncStatus.keyAt(j) == ident) {
576 mSyncStatus.remove(mSyncStatus.keyAt(j));
577 }
578 }
579 j = mSyncHistory.size();
580 while (j > 0) {
581 j--;
582 if (mSyncHistory.get(j).authorityId == ident) {
583 mSyncHistory.remove(j);
584 }
585 }
586 }
587 writeAccountInfoLocked();
588 writeStatusLocked();
589 writePendingOperationsLocked();
590 writeStatisticsLocked();
591 }
592 }
593 }
594
595 /**
596 * Called when the currently active sync is changing (there can only be
597 * one at a time). Either supply a valid ActiveSyncContext with information
598 * about the sync, or null to stop the currently active sync.
599 */
600 public void setActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
601 synchronized (mAuthorities) {
602 if (activeSyncContext != null) {
603 if (DEBUG) Log.v(TAG, "setActiveSync: account="
604 + activeSyncContext.mSyncOperation.account
605 + " auth=" + activeSyncContext.mSyncOperation.authority
606 + " src=" + activeSyncContext.mSyncOperation.syncSource
607 + " extras=" + activeSyncContext.mSyncOperation.extras);
608 if (mActiveSync != null) {
609 Log.w(TAG, "setActiveSync called with existing active sync!");
610 }
611 AuthorityInfo authority = getAuthorityLocked(
612 activeSyncContext.mSyncOperation.account,
613 activeSyncContext.mSyncOperation.authority,
614 "setActiveSync");
615 if (authority == null) {
616 return;
617 }
618 mActiveSync = new ActiveSyncInfo(authority.ident,
619 authority.account, authority.authority,
620 activeSyncContext.mStartTime);
621 } else {
622 if (DEBUG) Log.v(TAG, "setActiveSync: null");
623 mActiveSync = null;
624 }
625 }
626
Fred Quintanaac9385e2009-06-22 18:00:59 -0700627 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700628 }
629
630 /**
631 * To allow others to send active change reports, to poke clients.
632 */
633 public void reportActiveChange() {
Fred Quintanaac9385e2009-06-22 18:00:59 -0700634 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700635 }
636
637 /**
638 * Note that sync has started for the given account and authority.
639 */
Dianne Hackborn7a135592009-05-06 00:28:37 -0700640 public long insertStartSyncEvent(Account accountName, String authorityName,
Dianne Hackborn231cc602009-04-27 17:10:36 -0700641 long now, int source) {
642 long id;
643 synchronized (mAuthorities) {
644 if (DEBUG) Log.v(TAG, "insertStartSyncEvent: account=" + accountName
645 + " auth=" + authorityName + " source=" + source);
646 AuthorityInfo authority = getAuthorityLocked(accountName, authorityName,
647 "insertStartSyncEvent");
648 if (authority == null) {
649 return -1;
650 }
651 SyncHistoryItem item = new SyncHistoryItem();
652 item.authorityId = authority.ident;
653 item.historyId = mNextHistoryId++;
654 if (mNextHistoryId < 0) mNextHistoryId = 0;
655 item.eventTime = now;
656 item.source = source;
657 item.event = EVENT_START;
658 mSyncHistory.add(0, item);
659 while (mSyncHistory.size() > MAX_HISTORY) {
660 mSyncHistory.remove(mSyncHistory.size()-1);
661 }
662 id = item.historyId;
663 if (DEBUG) Log.v(TAG, "returning historyId " + id);
664 }
665
Fred Quintanaac9385e2009-06-22 18:00:59 -0700666 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700667 return id;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800668 }
669
670 public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage,
671 long downstreamActivity, long upstreamActivity) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700672 synchronized (mAuthorities) {
673 if (DEBUG) Log.v(TAG, "stopSyncEvent: historyId=" + historyId);
674 SyncHistoryItem item = null;
675 int i = mSyncHistory.size();
676 while (i > 0) {
677 i--;
678 item = mSyncHistory.get(i);
679 if (item.historyId == historyId) {
680 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800681 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700682 item = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800683 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700684
685 if (item == null) {
686 Log.w(TAG, "stopSyncEvent: no history for id " + historyId);
687 return;
688 }
689
690 item.elapsedTime = elapsedTime;
691 item.event = EVENT_STOP;
692 item.mesg = resultMessage;
693 item.downstreamActivity = downstreamActivity;
694 item.upstreamActivity = upstreamActivity;
695
696 SyncStatusInfo status = getOrCreateSyncStatusLocked(item.authorityId);
697
698 status.numSyncs++;
699 status.totalElapsedTime += elapsedTime;
700 switch (item.source) {
701 case SOURCE_LOCAL:
702 status.numSourceLocal++;
703 break;
704 case SOURCE_POLL:
705 status.numSourcePoll++;
706 break;
707 case SOURCE_USER:
708 status.numSourceUser++;
709 break;
710 case SOURCE_SERVER:
711 status.numSourceServer++;
712 break;
713 }
714
715 boolean writeStatisticsNow = false;
Dianne Hackborn55280a92009-05-07 15:53:46 -0700716 int day = getCurrentDayLocked();
Dianne Hackborn231cc602009-04-27 17:10:36 -0700717 if (mDayStats[0] == null) {
718 mDayStats[0] = new DayStats(day);
719 } else if (day != mDayStats[0].day) {
720 System.arraycopy(mDayStats, 0, mDayStats, 1, mDayStats.length-1);
721 mDayStats[0] = new DayStats(day);
722 writeStatisticsNow = true;
723 } else if (mDayStats[0] == null) {
724 }
725 final DayStats ds = mDayStats[0];
726
727 final long lastSyncTime = (item.eventTime + elapsedTime);
728 boolean writeStatusNow = false;
729 if (MESG_SUCCESS.equals(resultMessage)) {
730 // - if successful, update the successful columns
731 if (status.lastSuccessTime == 0 || status.lastFailureTime != 0) {
732 writeStatusNow = true;
733 }
734 status.lastSuccessTime = lastSyncTime;
735 status.lastSuccessSource = item.source;
736 status.lastFailureTime = 0;
737 status.lastFailureSource = -1;
738 status.lastFailureMesg = null;
739 status.initialFailureTime = 0;
740 ds.successCount++;
741 ds.successTime += elapsedTime;
742 } else if (!MESG_CANCELED.equals(resultMessage)) {
743 if (status.lastFailureTime == 0) {
744 writeStatusNow = true;
745 }
746 status.lastFailureTime = lastSyncTime;
747 status.lastFailureSource = item.source;
748 status.lastFailureMesg = resultMessage;
749 if (status.initialFailureTime == 0) {
750 status.initialFailureTime = lastSyncTime;
751 }
752 ds.failureCount++;
753 ds.failureTime += elapsedTime;
754 }
755
756 if (writeStatusNow) {
757 writeStatusLocked();
758 } else if (!hasMessages(MSG_WRITE_STATUS)) {
759 sendMessageDelayed(obtainMessage(MSG_WRITE_STATUS),
760 WRITE_STATUS_DELAY);
761 }
762 if (writeStatisticsNow) {
763 writeStatisticsLocked();
764 } else if (!hasMessages(MSG_WRITE_STATISTICS)) {
765 sendMessageDelayed(obtainMessage(MSG_WRITE_STATISTICS),
766 WRITE_STATISTICS_DELAY);
767 }
768 }
769
Fred Quintanaac9385e2009-06-22 18:00:59 -0700770 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700771 }
772
773 /**
774 * Return the currently active sync information, or null if there is no
775 * active sync. Note that the returned object is the real, live active
776 * sync object, so be careful what you do with it.
777 */
778 public ActiveSyncInfo getActiveSync() {
779 synchronized (mAuthorities) {
780 return mActiveSync;
781 }
782 }
783
784 /**
785 * Return an array of the current sync status for all authorities. Note
786 * that the objects inside the array are the real, live status objects,
787 * so be careful what you do with them.
788 */
789 public ArrayList<SyncStatusInfo> getSyncStatus() {
790 synchronized (mAuthorities) {
791 final int N = mSyncStatus.size();
792 ArrayList<SyncStatusInfo> ops = new ArrayList<SyncStatusInfo>(N);
793 for (int i=0; i<N; i++) {
794 ops.add(mSyncStatus.valueAt(i));
795 }
796 return ops;
797 }
798 }
799
800 /**
801 * Returns the status that matches the authority. If there are multiples accounts for
802 * the authority, the one with the latest "lastSuccessTime" status is returned.
803 * @param authority the authority whose row should be selected
804 * @return the SyncStatusInfo for the authority, or null if none exists
805 */
806 public SyncStatusInfo getStatusByAuthority(String authority) {
807 synchronized (mAuthorities) {
808 SyncStatusInfo best = null;
809 final int N = mSyncStatus.size();
810 for (int i=0; i<N; i++) {
811 SyncStatusInfo cur = mSyncStatus.get(i);
812 AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
813 if (ainfo != null && ainfo.authority.equals(authority)) {
814 if (best == null) {
815 best = cur;
816 } else if (best.lastSuccessTime > cur.lastSuccessTime) {
817 best = cur;
818 }
819 }
820 }
821 return best;
822 }
823 }
824
825 /**
826 * Return true if the pending status is true of any matching authorities.
827 */
Fred Quintanaac9385e2009-06-22 18:00:59 -0700828 public boolean isSyncPending(Account account, String authority) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700829 synchronized (mAuthorities) {
830 final int N = mSyncStatus.size();
831 for (int i=0; i<N; i++) {
832 SyncStatusInfo cur = mSyncStatus.get(i);
833 AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
834 if (ainfo == null) {
835 continue;
836 }
837 if (account != null && !ainfo.account.equals(account)) {
838 continue;
839 }
840 if (ainfo.authority.equals(authority) && cur.pending) {
841 return true;
842 }
843 }
844 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800845 }
846 }
847
848 /**
Dianne Hackborn231cc602009-04-27 17:10:36 -0700849 * Return an array of the current sync status for all authorities. Note
850 * that the objects inside the array are the real, live status objects,
851 * so be careful what you do with them.
852 */
853 public ArrayList<SyncHistoryItem> getSyncHistory() {
854 synchronized (mAuthorities) {
855 final int N = mSyncHistory.size();
856 ArrayList<SyncHistoryItem> items = new ArrayList<SyncHistoryItem>(N);
857 for (int i=0; i<N; i++) {
858 items.add(mSyncHistory.get(i));
859 }
860 return items;
861 }
862 }
863
864 /**
865 * Return an array of the current per-day statistics. Note
866 * that the objects inside the array are the real, live status objects,
867 * so be careful what you do with them.
868 */
869 public DayStats[] getDayStatistics() {
870 synchronized (mAuthorities) {
871 DayStats[] ds = new DayStats[mDayStats.length];
872 System.arraycopy(mDayStats, 0, ds, 0, ds.length);
873 return ds;
874 }
875 }
876
877 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800878 * If sync is failing for any of the provider/accounts then determine the time at which it
879 * started failing and return the earliest time over all the provider/accounts. If none are
880 * failing then return 0.
881 */
882 public long getInitialSyncFailureTime() {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700883 synchronized (mAuthorities) {
Fred Quintanaac9385e2009-06-22 18:00:59 -0700884 if (!mMasterSyncAutomatically) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700885 return 0;
886 }
887
888 long oldest = 0;
889 int i = mSyncStatus.size();
890 while (i > 0) {
891 i--;
892 SyncStatusInfo stats = mSyncStatus.valueAt(i);
893 AuthorityInfo authority = mAuthorities.get(stats.authorityId);
894 if (authority != null && authority.enabled) {
895 if (oldest == 0 || stats.initialFailureTime < oldest) {
896 oldest = stats.initialFailureTime;
897 }
898 }
899 }
900
901 return oldest;
902 }
903 }
904
Dianne Hackborn55280a92009-05-07 15:53:46 -0700905 private int getCurrentDayLocked() {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700906 mCal.setTimeInMillis(System.currentTimeMillis());
907 final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR);
908 if (mYear != mCal.get(Calendar.YEAR)) {
909 mYear = mCal.get(Calendar.YEAR);
910 mCal.clear();
911 mCal.set(Calendar.YEAR, mYear);
912 mYearInDays = (int)(mCal.getTimeInMillis()/86400000);
913 }
914 return dayOfYear + mYearInDays;
915 }
916
917 /**
918 * Retrieve an authority, returning null if one does not exist.
919 *
920 * @param accountName The name of the account for the authority.
921 * @param authorityName The name of the authority itself.
922 * @param tag If non-null, this will be used in a log message if the
923 * requested authority does not exist.
924 */
Dianne Hackborn7a135592009-05-06 00:28:37 -0700925 private AuthorityInfo getAuthorityLocked(Account accountName, String authorityName,
Dianne Hackborn231cc602009-04-27 17:10:36 -0700926 String tag) {
927 AccountInfo account = mAccounts.get(accountName);
928 if (account == null) {
929 if (tag != null) {
930 Log.w(TAG, tag + ": unknown account " + accountName);
931 }
932 return null;
933 }
934 AuthorityInfo authority = account.authorities.get(authorityName);
935 if (authority == null) {
936 if (tag != null) {
937 Log.w(TAG, tag + ": unknown authority " + authorityName);
938 }
939 return null;
940 }
941
942 return authority;
943 }
944
Dianne Hackborn7a135592009-05-06 00:28:37 -0700945 private AuthorityInfo getOrCreateAuthorityLocked(Account accountName,
Dianne Hackborn231cc602009-04-27 17:10:36 -0700946 String authorityName, int ident, boolean doWrite) {
947 AccountInfo account = mAccounts.get(accountName);
948 if (account == null) {
949 account = new AccountInfo(accountName);
950 mAccounts.put(accountName, account);
951 }
952 AuthorityInfo authority = account.authorities.get(authorityName);
953 if (authority == null) {
954 if (ident < 0) {
955 // Look for a new identifier for this authority.
956 final int N = mAuthorities.size();
957 ident = 0;
958 for (int i=0; i<N; i++) {
959 if (mAuthorities.valueAt(i).ident > ident) {
960 break;
961 }
962 ident++;
963 }
964 }
965 authority = new AuthorityInfo(accountName, authorityName, ident);
966 account.authorities.put(authorityName, authority);
967 mAuthorities.put(ident, authority);
968 if (doWrite) {
969 writeAccountInfoLocked();
970 }
971 }
972
973 return authority;
974 }
975
976 private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) {
977 SyncStatusInfo status = mSyncStatus.get(authorityId);
978 if (status == null) {
979 status = new SyncStatusInfo(authorityId);
980 mSyncStatus.put(authorityId, status);
981 }
982 return status;
983 }
984
Dianne Hackborn55280a92009-05-07 15:53:46 -0700985 public void writeAllState() {
986 synchronized (mAuthorities) {
987 // Account info is always written so no need to do it here.
988
989 if (mNumPendingFinished > 0) {
990 // Only write these if they are out of date.
991 writePendingOperationsLocked();
992 }
993
994 // Just always write these... they are likely out of date.
995 writeStatusLocked();
996 writeStatisticsLocked();
997 }
998 }
999
Dianne Hackborn231cc602009-04-27 17:10:36 -07001000 /**
1001 * Read all account information back in to the initial engine state.
1002 */
1003 private void readAccountInfoLocked() {
1004 FileInputStream fis = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001005 try {
Dianne Hackborn231cc602009-04-27 17:10:36 -07001006 fis = mAccountInfoFile.openRead();
1007 if (DEBUG_FILE) Log.v(TAG, "Reading " + mAccountInfoFile.getBaseFile());
1008 XmlPullParser parser = Xml.newPullParser();
1009 parser.setInput(fis, null);
1010 int eventType = parser.getEventType();
1011 while (eventType != XmlPullParser.START_TAG) {
1012 eventType = parser.next();
1013 }
1014 String tagName = parser.getName();
1015 if ("accounts".equals(tagName)) {
1016 String listen = parser.getAttributeValue(
1017 null, "listen-for-tickles");
Fred Quintanaac9385e2009-06-22 18:00:59 -07001018 mMasterSyncAutomatically = listen == null
Dianne Hackborn231cc602009-04-27 17:10:36 -07001019 || Boolean.parseBoolean(listen);
1020 eventType = parser.next();
1021 do {
1022 if (eventType == XmlPullParser.START_TAG
1023 && parser.getDepth() == 2) {
1024 tagName = parser.getName();
1025 if ("authority".equals(tagName)) {
1026 int id = -1;
1027 try {
1028 id = Integer.parseInt(parser.getAttributeValue(
1029 null, "id"));
1030 } catch (NumberFormatException e) {
1031 } catch (NullPointerException e) {
1032 }
1033 if (id >= 0) {
1034 String accountName = parser.getAttributeValue(
1035 null, "account");
Dianne Hackborn7a135592009-05-06 00:28:37 -07001036 String accountType = parser.getAttributeValue(
1037 null, "type");
1038 if (accountType == null) {
1039 accountType = "com.google.GAIA";
1040 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001041 String authorityName = parser.getAttributeValue(
1042 null, "authority");
1043 String enabled = parser.getAttributeValue(
1044 null, "enabled");
1045 AuthorityInfo authority = mAuthorities.get(id);
1046 if (DEBUG_FILE) Log.v(TAG, "Adding authority: account="
1047 + accountName + " auth=" + authorityName
1048 + " enabled=" + enabled);
1049 if (authority == null) {
1050 if (DEBUG_FILE) Log.v(TAG, "Creating entry");
1051 authority = getOrCreateAuthorityLocked(
Dianne Hackborn7a135592009-05-06 00:28:37 -07001052 new Account(accountName, accountType),
1053 authorityName, id, false);
Dianne Hackborn231cc602009-04-27 17:10:36 -07001054 }
1055 if (authority != null) {
1056 authority.enabled = enabled == null
1057 || Boolean.parseBoolean(enabled);
1058 } else {
1059 Log.w(TAG, "Failure adding authority: account="
1060 + accountName + " auth=" + authorityName
1061 + " enabled=" + enabled);
1062 }
1063 }
1064 }
1065 }
1066 eventType = parser.next();
1067 } while (eventType != XmlPullParser.END_DOCUMENT);
1068 }
1069 } catch (XmlPullParserException e) {
1070 Log.w(TAG, "Error reading accounts", e);
1071 } catch (java.io.IOException e) {
1072 if (fis == null) Log.i(TAG, "No initial accounts");
1073 else Log.w(TAG, "Error reading accounts", e);
1074 } finally {
1075 if (fis != null) {
1076 try {
1077 fis.close();
1078 } catch (java.io.IOException e1) {
1079 }
1080 }
1081 }
1082 }
1083
1084 /**
1085 * Write all account information to the account file.
1086 */
1087 private void writeAccountInfoLocked() {
1088 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mAccountInfoFile.getBaseFile());
1089 FileOutputStream fos = null;
1090
1091 try {
1092 fos = mAccountInfoFile.startWrite();
1093 XmlSerializer out = new FastXmlSerializer();
1094 out.setOutput(fos, "utf-8");
1095 out.startDocument(null, true);
1096 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
1097
1098 out.startTag(null, "accounts");
Fred Quintanaac9385e2009-06-22 18:00:59 -07001099 if (!mMasterSyncAutomatically) {
Dianne Hackborn231cc602009-04-27 17:10:36 -07001100 out.attribute(null, "listen-for-tickles", "false");
1101 }
1102
1103 final int N = mAuthorities.size();
1104 for (int i=0; i<N; i++) {
1105 AuthorityInfo authority = mAuthorities.get(i);
1106 out.startTag(null, "authority");
1107 out.attribute(null, "id", Integer.toString(authority.ident));
Dianne Hackborn7a135592009-05-06 00:28:37 -07001108 out.attribute(null, "account", authority.account.mName);
1109 out.attribute(null, "type", authority.account.mType);
Dianne Hackborn231cc602009-04-27 17:10:36 -07001110 out.attribute(null, "authority", authority.authority);
1111 if (!authority.enabled) {
1112 out.attribute(null, "enabled", "false");
1113 }
1114 out.endTag(null, "authority");
1115 }
1116
1117 out.endTag(null, "accounts");
1118
1119 out.endDocument();
1120
1121 mAccountInfoFile.finishWrite(fos);
1122 } catch (java.io.IOException e1) {
1123 Log.w(TAG, "Error writing accounts", e1);
1124 if (fos != null) {
1125 mAccountInfoFile.failWrite(fos);
1126 }
1127 }
1128 }
1129
1130 static int getIntColumn(Cursor c, String name) {
1131 return c.getInt(c.getColumnIndex(name));
1132 }
1133
1134 static long getLongColumn(Cursor c, String name) {
1135 return c.getLong(c.getColumnIndex(name));
1136 }
1137
1138 /**
1139 * Load sync engine state from the old syncmanager database, and then
1140 * erase it. Note that we don't deal with pending operations, active
1141 * sync, or history.
1142 */
1143 private void readLegacyAccountInfoLocked() {
1144 // Look for old database to initialize from.
1145 File file = mContext.getDatabasePath("syncmanager.db");
1146 if (!file.exists()) {
1147 return;
1148 }
1149 String path = file.getPath();
1150 SQLiteDatabase db = null;
1151 try {
1152 db = SQLiteDatabase.openDatabase(path, null,
1153 SQLiteDatabase.OPEN_READONLY);
1154 } catch (SQLiteException e) {
1155 }
1156
1157 if (db != null) {
Dianne Hackborn2d5ed1f2009-05-06 15:22:38 -07001158 final boolean hasType = db.getVersion() >= 11;
1159
Dianne Hackborn231cc602009-04-27 17:10:36 -07001160 // Copy in all of the status information, as well as accounts.
1161 if (DEBUG_FILE) Log.v(TAG, "Reading legacy sync accounts db");
1162 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
1163 qb.setTables("stats, status");
1164 HashMap<String,String> map = new HashMap<String,String>();
1165 map.put("_id", "status._id as _id");
1166 map.put("account", "stats.account as account");
Dianne Hackborn2d5ed1f2009-05-06 15:22:38 -07001167 if (hasType) {
1168 map.put("account_type", "stats.account_type as account_type");
1169 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001170 map.put("authority", "stats.authority as authority");
1171 map.put("totalElapsedTime", "totalElapsedTime");
1172 map.put("numSyncs", "numSyncs");
1173 map.put("numSourceLocal", "numSourceLocal");
1174 map.put("numSourcePoll", "numSourcePoll");
1175 map.put("numSourceServer", "numSourceServer");
1176 map.put("numSourceUser", "numSourceUser");
1177 map.put("lastSuccessSource", "lastSuccessSource");
1178 map.put("lastSuccessTime", "lastSuccessTime");
1179 map.put("lastFailureSource", "lastFailureSource");
1180 map.put("lastFailureTime", "lastFailureTime");
1181 map.put("lastFailureMesg", "lastFailureMesg");
1182 map.put("pending", "pending");
1183 qb.setProjectionMap(map);
1184 qb.appendWhere("stats._id = status.stats_id");
1185 Cursor c = qb.query(db, null, null, null, null, null, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001186 while (c.moveToNext()) {
Dianne Hackborn231cc602009-04-27 17:10:36 -07001187 String accountName = c.getString(c.getColumnIndex("account"));
Dianne Hackborn2d5ed1f2009-05-06 15:22:38 -07001188 String accountType = hasType
1189 ? c.getString(c.getColumnIndex("account_type")) : null;
Dianne Hackborn7a135592009-05-06 00:28:37 -07001190 if (accountType == null) {
1191 accountType = "com.google.GAIA";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001192 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001193 String authorityName = c.getString(c.getColumnIndex("authority"));
1194 AuthorityInfo authority = this.getOrCreateAuthorityLocked(
Dianne Hackborn7a135592009-05-06 00:28:37 -07001195 new Account(accountName, accountType),
1196 authorityName, -1, false);
Dianne Hackborn231cc602009-04-27 17:10:36 -07001197 if (authority != null) {
1198 int i = mSyncStatus.size();
1199 boolean found = false;
1200 SyncStatusInfo st = null;
1201 while (i > 0) {
1202 i--;
1203 st = mSyncStatus.get(i);
1204 if (st.authorityId == authority.ident) {
1205 found = true;
1206 break;
1207 }
1208 }
1209 if (!found) {
1210 st = new SyncStatusInfo(authority.ident);
1211 mSyncStatus.put(authority.ident, st);
1212 }
1213 st.totalElapsedTime = getLongColumn(c, "totalElapsedTime");
1214 st.numSyncs = getIntColumn(c, "numSyncs");
1215 st.numSourceLocal = getIntColumn(c, "numSourceLocal");
1216 st.numSourcePoll = getIntColumn(c, "numSourcePoll");
1217 st.numSourceServer = getIntColumn(c, "numSourceServer");
1218 st.numSourceUser = getIntColumn(c, "numSourceUser");
1219 st.lastSuccessSource = getIntColumn(c, "lastSuccessSource");
1220 st.lastSuccessTime = getLongColumn(c, "lastSuccessTime");
1221 st.lastFailureSource = getIntColumn(c, "lastFailureSource");
1222 st.lastFailureTime = getLongColumn(c, "lastFailureTime");
1223 st.lastFailureMesg = c.getString(c.getColumnIndex("lastFailureMesg"));
1224 st.pending = getIntColumn(c, "pending") != 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001225 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001226 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001227
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001228 c.close();
Dianne Hackborn231cc602009-04-27 17:10:36 -07001229
1230 // Retrieve the settings.
1231 qb = new SQLiteQueryBuilder();
1232 qb.setTables("settings");
1233 c = qb.query(db, null, null, null, null, null, null);
1234 while (c.moveToNext()) {
1235 String name = c.getString(c.getColumnIndex("name"));
1236 String value = c.getString(c.getColumnIndex("value"));
1237 if (name == null) continue;
1238 if (name.equals("listen_for_tickles")) {
Fred Quintanaac9385e2009-06-22 18:00:59 -07001239 setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value));
Dianne Hackborn231cc602009-04-27 17:10:36 -07001240 } else if (name.startsWith("sync_provider_")) {
1241 String provider = name.substring("sync_provider_".length(),
1242 name.length());
Fred Quintanaac9385e2009-06-22 18:00:59 -07001243 int i = mAuthorities.size();
1244 while (i > 0) {
1245 i--;
1246 AuthorityInfo authority = mAuthorities.get(i);
1247 if (authority.authority.equals(provider)) {
1248 authority.enabled = value == null || Boolean.parseBoolean(value);
1249 }
1250 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001251 }
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}