blob: 8cc06426c5997d58fcbe31d6aff4b2ea47c4ce38 [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;
Amith Yamasani70c874b2009-07-06 14:53:25 -070028import android.backup.IBackupManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080029import android.database.Cursor;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.database.sqlite.SQLiteDatabase;
Dianne Hackborn231cc602009-04-27 17:10:36 -070031import android.database.sqlite.SQLiteException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import android.database.sqlite.SQLiteQueryBuilder;
Dianne Hackborn231cc602009-04-27 17:10:36 -070033import android.os.Bundle;
34import android.os.Environment;
35import android.os.Handler;
36import android.os.Message;
37import android.os.Parcel;
38import android.os.RemoteCallbackList;
39import android.os.RemoteException;
Amith Yamasani70c874b2009-07-06 14:53:25 -070040import android.os.ServiceManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041import android.util.Log;
Dianne Hackborn231cc602009-04-27 17:10:36 -070042import android.util.SparseArray;
43import android.util.Xml;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044
Dianne Hackborn231cc602009-04-27 17:10:36 -070045import java.io.File;
46import java.io.FileInputStream;
47import java.io.FileOutputStream;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048import java.util.ArrayList;
Dianne Hackborn231cc602009-04-27 17:10:36 -070049import java.util.Calendar;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080050import java.util.HashMap;
Dianne Hackborn231cc602009-04-27 17:10:36 -070051import java.util.Iterator;
52import java.util.TimeZone;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080053
54/**
Dianne Hackborn231cc602009-04-27 17:10:36 -070055 * Singleton that tracks the sync data and overall sync
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056 * history on the device.
57 *
58 * @hide
59 */
Dianne Hackborn231cc602009-04-27 17:10:36 -070060public class SyncStorageEngine extends Handler {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061 private static final String TAG = "SyncManager";
Dianne Hackborn231cc602009-04-27 17:10:36 -070062 private static final boolean DEBUG = false;
63 private static final boolean DEBUG_FILE = false;
64
65 // @VisibleForTesting
66 static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080067
Dianne Hackborn231cc602009-04-27 17:10:36 -070068 /** Enum value for a sync start event. */
69 public static final int EVENT_START = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080070
Dianne Hackborn231cc602009-04-27 17:10:36 -070071 /** Enum value for a sync stop event. */
72 public static final int EVENT_STOP = 1;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080073
Dianne Hackborn231cc602009-04-27 17:10:36 -070074 // TODO: i18n -- grab these out of resources.
75 /** String names for the sync event types. */
76 public static final String[] EVENTS = { "START", "STOP" };
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080077
Dianne Hackborn231cc602009-04-27 17:10:36 -070078 /** Enum value for a server-initiated sync. */
79 public static final int SOURCE_SERVER = 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080080
Dianne Hackborn231cc602009-04-27 17:10:36 -070081 /** Enum value for a local-initiated sync. */
82 public static final int SOURCE_LOCAL = 1;
83 /**
84 * Enum value for a poll-based sync (e.g., upon connection to
85 * network)
86 */
87 public static final int SOURCE_POLL = 2;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088
Dianne Hackborn231cc602009-04-27 17:10:36 -070089 /** Enum value for a user-initiated sync. */
90 public static final int SOURCE_USER = 3;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080091
Fred Quintanaac9385e2009-06-22 18:00:59 -070092 private static final Intent SYNC_CONNECTION_SETTING_CHANGED_INTENT =
93 new Intent("com.android.sync.SYNC_CONN_STATUS_CHANGED");
94
Dianne Hackborn231cc602009-04-27 17:10:36 -070095 // TODO: i18n -- grab these out of resources.
96 /** String names for the sync source types. */
97 public static final String[] SOURCES = { "SERVER",
98 "LOCAL",
99 "POLL",
100 "USER" };
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800101
Dianne Hackborn231cc602009-04-27 17:10:36 -0700102 // The MESG column will contain one of these or one of the Error types.
103 public static final String MESG_SUCCESS = "success";
104 public static final String MESG_CANCELED = "canceled";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800105
Dianne Hackborn231cc602009-04-27 17:10:36 -0700106 public static final int MAX_HISTORY = 15;
107
108 private static final int MSG_WRITE_STATUS = 1;
109 private static final long WRITE_STATUS_DELAY = 1000*60*10; // 10 minutes
110
111 private static final int MSG_WRITE_STATISTICS = 2;
112 private static final long WRITE_STATISTICS_DELAY = 1000*60*30; // 1/2 hour
Joe Onorato8294fad2009-07-15 16:08:44 -0700113
114 private static final boolean SYNC_ENABLED_DEFAULT = false;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700115
116 public static class PendingOperation {
Dianne Hackborn7a135592009-05-06 00:28:37 -0700117 final Account account;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700118 final int syncSource;
119 final String authority;
120 final Bundle extras; // note: read-only.
121
122 int authorityId;
123 byte[] flatExtras;
124
Dianne Hackborn7a135592009-05-06 00:28:37 -0700125 PendingOperation(Account account, int source,
Dianne Hackborn231cc602009-04-27 17:10:36 -0700126 String authority, Bundle extras) {
127 this.account = account;
128 this.syncSource = source;
129 this.authority = authority;
130 this.extras = extras != null ? new Bundle(extras) : extras;
131 this.authorityId = -1;
132 }
133
134 PendingOperation(PendingOperation other) {
135 this.account = other.account;
136 this.syncSource = other.syncSource;
137 this.authority = other.authority;
138 this.extras = other.extras;
139 this.authorityId = other.authorityId;
140 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800141 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700142
143 static class AccountInfo {
Dianne Hackborn7a135592009-05-06 00:28:37 -0700144 final Account account;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700145 final HashMap<String, AuthorityInfo> authorities =
146 new HashMap<String, AuthorityInfo>();
147
Dianne Hackborn7a135592009-05-06 00:28:37 -0700148 AccountInfo(Account account) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700149 this.account = account;
150 }
151 }
152
153 public static class AuthorityInfo {
Dianne Hackborn7a135592009-05-06 00:28:37 -0700154 final Account account;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700155 final String authority;
156 final int ident;
157 boolean enabled;
Fred Quintanaac9385e2009-06-22 18:00:59 -0700158
Dianne Hackborn7a135592009-05-06 00:28:37 -0700159 AuthorityInfo(Account account, String authority, int ident) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700160 this.account = account;
161 this.authority = authority;
162 this.ident = ident;
Joe Onorato8294fad2009-07-15 16:08:44 -0700163 enabled = SYNC_ENABLED_DEFAULT;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700164 }
165 }
166
167 public static class SyncHistoryItem {
168 int authorityId;
169 int historyId;
170 long eventTime;
171 long elapsedTime;
172 int source;
173 int event;
174 long upstreamActivity;
175 long downstreamActivity;
176 String mesg;
177 }
178
179 public static class DayStats {
180 public final int day;
181 public int successCount;
182 public long successTime;
183 public int failureCount;
184 public long failureTime;
185
186 public DayStats(int day) {
187 this.day = day;
188 }
189 }
190
191 // Primary list of all syncable authorities. Also our global lock.
192 private final SparseArray<AuthorityInfo> mAuthorities =
193 new SparseArray<AuthorityInfo>();
194
Dianne Hackborn7a135592009-05-06 00:28:37 -0700195 private final HashMap<Account, AccountInfo> mAccounts =
196 new HashMap<Account, AccountInfo>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800197
Dianne Hackborn231cc602009-04-27 17:10:36 -0700198 private final ArrayList<PendingOperation> mPendingOperations =
199 new ArrayList<PendingOperation>();
200
201 private ActiveSyncInfo mActiveSync;
202
203 private final SparseArray<SyncStatusInfo> mSyncStatus =
204 new SparseArray<SyncStatusInfo>();
205
206 private final ArrayList<SyncHistoryItem> mSyncHistory =
207 new ArrayList<SyncHistoryItem>();
208
209 private final RemoteCallbackList<ISyncStatusObserver> mChangeListeners
210 = new RemoteCallbackList<ISyncStatusObserver>();
211
212 // We keep 4 weeks of stats.
213 private final DayStats[] mDayStats = new DayStats[7*4];
214 private final Calendar mCal;
215 private int mYear;
216 private int mYearInDays;
217
218 private final Context mContext;
219 private static volatile SyncStorageEngine sSyncStorageEngine = null;
220
221 /**
222 * This file contains the core engine state: all accounts and the
223 * settings for them. It must never be lost, and should be changed
224 * infrequently, so it is stored as an XML file.
225 */
226 private final AtomicFile mAccountInfoFile;
227
228 /**
229 * This file contains the current sync status. We would like to retain
230 * it across boots, but its loss is not the end of the world, so we store
231 * this information as binary data.
232 */
233 private final AtomicFile mStatusFile;
234
235 /**
236 * This file contains sync statistics. This is purely debugging information
237 * so is written infrequently and can be thrown away at any time.
238 */
239 private final AtomicFile mStatisticsFile;
240
241 /**
242 * This file contains the pending sync operations. It is a binary file,
243 * which must be updated every time an operation is added or removed,
244 * so we have special handling of it.
245 */
246 private final AtomicFile mPendingFile;
247 private static final int PENDING_FINISH_TO_WRITE = 4;
248 private int mNumPendingFinished = 0;
249
250 private int mNextHistoryId = 0;
Fred Quintanaac9385e2009-06-22 18:00:59 -0700251 private boolean mMasterSyncAutomatically = true;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700252
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800253 private SyncStorageEngine(Context context) {
254 mContext = context;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800255 sSyncStorageEngine = this;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700256
257 mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
258
259 File dataDir = Environment.getDataDirectory();
260 File systemDir = new File(dataDir, "system");
261 File syncDir = new File(systemDir, "sync");
262 mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
263 mStatusFile = new AtomicFile(new File(syncDir, "status.bin"));
264 mPendingFile = new AtomicFile(new File(syncDir, "pending.bin"));
265 mStatisticsFile = new AtomicFile(new File(syncDir, "stats.bin"));
266
267 readAccountInfoLocked();
268 readStatusLocked();
269 readPendingOperationsLocked();
270 readStatisticsLocked();
271 readLegacyAccountInfoLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800272 }
273
274 public static SyncStorageEngine newTestInstance(Context context) {
275 return new SyncStorageEngine(context);
276 }
277
278 public static void init(Context context) {
279 if (sSyncStorageEngine != null) {
280 throw new IllegalStateException("already initialized");
281 }
282 sSyncStorageEngine = new SyncStorageEngine(context);
283 }
284
285 public static SyncStorageEngine getSingleton() {
286 if (sSyncStorageEngine == null) {
287 throw new IllegalStateException("not initialized");
288 }
289 return sSyncStorageEngine;
290 }
291
Dianne Hackborn231cc602009-04-27 17:10:36 -0700292 @Override public void handleMessage(Message msg) {
293 if (msg.what == MSG_WRITE_STATUS) {
294 synchronized (mAccounts) {
295 writeStatusLocked();
Fred Quintanad9d2f112009-04-23 13:36:27 -0700296 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700297 } else if (msg.what == MSG_WRITE_STATISTICS) {
298 synchronized (mAccounts) {
299 writeStatisticsLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800300 }
301 }
302 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700303
304 public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
305 synchronized (mAuthorities) {
306 mChangeListeners.register(callback, mask);
307 }
308 }
309
310 public void removeStatusChangeListener(ISyncStatusObserver callback) {
311 synchronized (mAuthorities) {
312 mChangeListeners.unregister(callback);
313 }
314 }
315
316 private void reportChange(int which) {
317 ArrayList<ISyncStatusObserver> reports = null;
318 synchronized (mAuthorities) {
319 int i = mChangeListeners.beginBroadcast();
320 while (i > 0) {
321 i--;
322 Integer mask = (Integer)mChangeListeners.getBroadcastCookie(i);
323 if ((which & mask.intValue()) == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800324 continue;
325 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700326 if (reports == null) {
327 reports = new ArrayList<ISyncStatusObserver>(i);
328 }
329 reports.add(mChangeListeners.getBroadcastItem(i));
330 }
Dianne Hackbornb06ea702009-07-13 13:07:51 -0700331 mChangeListeners.finishBroadcast();
Dianne Hackborn231cc602009-04-27 17:10:36 -0700332 }
333
334 if (DEBUG) Log.v(TAG, "reportChange " + which + " to: " + reports);
335
336 if (reports != null) {
337 int i = reports.size();
338 while (i > 0) {
339 i--;
340 try {
341 reports.get(i).onStatusChanged(which);
342 } catch (RemoteException e) {
343 // The remote callback list will take care of this for us.
344 }
345 }
346 }
Amith Yamasani70c874b2009-07-06 14:53:25 -0700347 // Inform the backup manager about a data change
348 IBackupManager ibm = IBackupManager.Stub.asInterface(
349 ServiceManager.getService(Context.BACKUP_SERVICE));
350 if (ibm != null) {
351 try {
352 ibm.dataChanged("com.android.providers.settings");
353 } catch (RemoteException e) {
354 // Try again later
355 }
356 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700357 }
Amith Yamasani70c874b2009-07-06 14:53:25 -0700358
Fred Quintanaac9385e2009-06-22 18:00:59 -0700359 public boolean getSyncAutomatically(Account account, String providerName) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700360 synchronized (mAuthorities) {
361 if (account != null) {
362 AuthorityInfo authority = getAuthorityLocked(account, providerName,
Fred Quintanaac9385e2009-06-22 18:00:59 -0700363 "getSyncAutomatically");
364 return authority != null && authority.enabled;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700365 }
Fred Quintanaac9385e2009-06-22 18:00:59 -0700366
Dianne Hackborn231cc602009-04-27 17:10:36 -0700367 int i = mAuthorities.size();
368 while (i > 0) {
369 i--;
370 AuthorityInfo authority = mAuthorities.get(i);
371 if (authority.authority.equals(providerName)
372 && authority.enabled) {
373 return true;
374 }
375 }
376 return false;
377 }
378 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800379
Fred Quintanaac9385e2009-06-22 18:00:59 -0700380 public void setSyncAutomatically(Account account, String providerName, boolean sync) {
Joe Onorato8294fad2009-07-15 16:08:44 -0700381 boolean wasEnabled;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700382 synchronized (mAuthorities) {
Joe Onorato8294fad2009-07-15 16:08:44 -0700383 AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false);
384 wasEnabled = authority.enabled;
385 authority.enabled = sync;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700386 writeAccountInfoLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800387 }
Joe Onorato8294fad2009-07-15 16:08:44 -0700388
389 if (!wasEnabled && sync) {
390 mContext.getContentResolver().requestSync(account, providerName, new Bundle());
391 }
Fred Quintanaac9385e2009-06-22 18:00:59 -0700392 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800393 }
394
Fred Quintanaac9385e2009-06-22 18:00:59 -0700395 public void setMasterSyncAutomatically(boolean flag) {
Joe Onorato8294fad2009-07-15 16:08:44 -0700396 boolean old;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700397 synchronized (mAuthorities) {
Joe Onorato8294fad2009-07-15 16:08:44 -0700398 old = mMasterSyncAutomatically;
Fred Quintanaac9385e2009-06-22 18:00:59 -0700399 mMasterSyncAutomatically = flag;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700400 writeAccountInfoLocked();
401 }
Joe Onorato8294fad2009-07-15 16:08:44 -0700402 if (!old && flag) {
403 mContext.getContentResolver().requestSync(null, null, new Bundle());
404 }
Fred Quintanaac9385e2009-06-22 18:00:59 -0700405 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
406 mContext.sendBroadcast(SYNC_CONNECTION_SETTING_CHANGED_INTENT);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700407 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800408
Fred Quintanaac9385e2009-06-22 18:00:59 -0700409 public boolean getMasterSyncAutomatically() {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700410 synchronized (mAuthorities) {
Fred Quintanaac9385e2009-06-22 18:00:59 -0700411 return mMasterSyncAutomatically;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700412 }
413 }
414
Dianne Hackborn7a135592009-05-06 00:28:37 -0700415 public AuthorityInfo getAuthority(Account account, String authority) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700416 synchronized (mAuthorities) {
417 return getAuthorityLocked(account, authority, null);
418 }
419 }
420
421 public AuthorityInfo getAuthority(int authorityId) {
422 synchronized (mAuthorities) {
423 return mAuthorities.get(authorityId);
424 }
425 }
426
427 /**
428 * Returns true if there is currently a sync operation for the given
429 * account or authority in the pending list, or actively being processed.
430 */
Dianne Hackborn7a135592009-05-06 00:28:37 -0700431 public boolean isSyncActive(Account account, String authority) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700432 synchronized (mAuthorities) {
433 int i = mPendingOperations.size();
434 while (i > 0) {
435 i--;
436 // TODO(fredq): this probably shouldn't be considering
437 // pending operations.
438 PendingOperation op = mPendingOperations.get(i);
439 if (op.account.equals(account) && op.authority.equals(authority)) {
440 return true;
441 }
442 }
443
444 if (mActiveSync != null) {
445 AuthorityInfo ainfo = getAuthority(mActiveSync.authorityId);
446 if (ainfo != null && ainfo.account.equals(account)
447 && ainfo.authority.equals(authority)) {
448 return true;
449 }
450 }
451 }
452
453 return false;
454 }
455
456 public PendingOperation insertIntoPending(PendingOperation op) {
457 synchronized (mAuthorities) {
458 if (DEBUG) Log.v(TAG, "insertIntoPending: account=" + op.account
459 + " auth=" + op.authority
460 + " src=" + op.syncSource
461 + " extras=" + op.extras);
462
463 AuthorityInfo authority = getOrCreateAuthorityLocked(op.account,
464 op.authority,
465 -1 /* desired identifier */,
466 true /* write accounts to storage */);
467 if (authority == null) {
468 return null;
469 }
470
471 op = new PendingOperation(op);
472 op.authorityId = authority.ident;
473 mPendingOperations.add(op);
474 appendPendingOperationLocked(op);
475
476 SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
477 status.pending = true;
478 }
479
Fred Quintanaac9385e2009-06-22 18:00:59 -0700480 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700481 return op;
482 }
483
484 public boolean deleteFromPending(PendingOperation op) {
485 boolean res = false;
486 synchronized (mAuthorities) {
487 if (DEBUG) Log.v(TAG, "deleteFromPending: account=" + op.account
488 + " auth=" + op.authority
489 + " src=" + op.syncSource
490 + " extras=" + op.extras);
491 if (mPendingOperations.remove(op)) {
492 if (mPendingOperations.size() == 0
493 || mNumPendingFinished >= PENDING_FINISH_TO_WRITE) {
494 writePendingOperationsLocked();
495 mNumPendingFinished = 0;
496 } else {
497 mNumPendingFinished++;
498 }
499
500 AuthorityInfo authority = getAuthorityLocked(op.account, op.authority,
501 "deleteFromPending");
502 if (authority != null) {
503 if (DEBUG) Log.v(TAG, "removing - " + authority);
504 final int N = mPendingOperations.size();
505 boolean morePending = false;
506 for (int i=0; i<N; i++) {
507 PendingOperation cur = mPendingOperations.get(i);
508 if (cur.account.equals(op.account)
509 && cur.authority.equals(op.authority)) {
510 morePending = true;
511 break;
512 }
513 }
514
515 if (!morePending) {
516 if (DEBUG) Log.v(TAG, "no more pending!");
517 SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
518 status.pending = false;
519 }
520 }
521
522 res = true;
523 }
524 }
525
Fred Quintanaac9385e2009-06-22 18:00:59 -0700526 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700527 return res;
528 }
529
530 public int clearPending() {
531 int num;
532 synchronized (mAuthorities) {
533 if (DEBUG) Log.v(TAG, "clearPending");
534 num = mPendingOperations.size();
535 mPendingOperations.clear();
536 final int N = mSyncStatus.size();
537 for (int i=0; i<N; i++) {
538 mSyncStatus.get(i).pending = false;
539 }
540 writePendingOperationsLocked();
541 }
Fred Quintanaac9385e2009-06-22 18:00:59 -0700542 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700543 return num;
544 }
545
546 /**
547 * Return a copy of the current array of pending operations. The
548 * PendingOperation objects are the real objects stored inside, so that
549 * they can be used with deleteFromPending().
550 */
551 public ArrayList<PendingOperation> getPendingOperations() {
552 synchronized (mAuthorities) {
553 return new ArrayList<PendingOperation>(mPendingOperations);
554 }
555 }
556
557 /**
558 * Return the number of currently pending operations.
559 */
560 public int getPendingOperationCount() {
561 synchronized (mAuthorities) {
562 return mPendingOperations.size();
563 }
564 }
565
566 /**
567 * Called when the set of account has changed, given the new array of
568 * active accounts.
569 */
Dianne Hackborn7a135592009-05-06 00:28:37 -0700570 public void doDatabaseCleanup(Account[] accounts) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700571 synchronized (mAuthorities) {
572 if (DEBUG) Log.w(TAG, "Updating for new accounts...");
573 SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>();
574 Iterator<AccountInfo> accIt = mAccounts.values().iterator();
575 while (accIt.hasNext()) {
576 AccountInfo acc = accIt.next();
577 if (!ArrayUtils.contains(accounts, acc.account)) {
578 // This account no longer exists...
579 if (DEBUG) Log.w(TAG, "Account removed: " + acc.account);
580 for (AuthorityInfo auth : acc.authorities.values()) {
581 removing.put(auth.ident, auth);
582 }
583 accIt.remove();
584 }
585 }
586
587 // Clean out all data structures.
588 int i = removing.size();
589 if (i > 0) {
590 while (i > 0) {
591 i--;
592 int ident = removing.keyAt(i);
593 mAuthorities.remove(ident);
594 int j = mSyncStatus.size();
595 while (j > 0) {
596 j--;
597 if (mSyncStatus.keyAt(j) == ident) {
598 mSyncStatus.remove(mSyncStatus.keyAt(j));
599 }
600 }
601 j = mSyncHistory.size();
602 while (j > 0) {
603 j--;
604 if (mSyncHistory.get(j).authorityId == ident) {
605 mSyncHistory.remove(j);
606 }
607 }
608 }
609 writeAccountInfoLocked();
610 writeStatusLocked();
611 writePendingOperationsLocked();
612 writeStatisticsLocked();
613 }
614 }
615 }
616
617 /**
618 * Called when the currently active sync is changing (there can only be
619 * one at a time). Either supply a valid ActiveSyncContext with information
620 * about the sync, or null to stop the currently active sync.
621 */
622 public void setActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
623 synchronized (mAuthorities) {
624 if (activeSyncContext != null) {
625 if (DEBUG) Log.v(TAG, "setActiveSync: account="
626 + activeSyncContext.mSyncOperation.account
627 + " auth=" + activeSyncContext.mSyncOperation.authority
628 + " src=" + activeSyncContext.mSyncOperation.syncSource
629 + " extras=" + activeSyncContext.mSyncOperation.extras);
630 if (mActiveSync != null) {
631 Log.w(TAG, "setActiveSync called with existing active sync!");
632 }
633 AuthorityInfo authority = getAuthorityLocked(
634 activeSyncContext.mSyncOperation.account,
635 activeSyncContext.mSyncOperation.authority,
636 "setActiveSync");
637 if (authority == null) {
638 return;
639 }
640 mActiveSync = new ActiveSyncInfo(authority.ident,
641 authority.account, authority.authority,
642 activeSyncContext.mStartTime);
643 } else {
644 if (DEBUG) Log.v(TAG, "setActiveSync: null");
645 mActiveSync = null;
646 }
647 }
648
Fred Quintanaac9385e2009-06-22 18:00:59 -0700649 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700650 }
651
652 /**
653 * To allow others to send active change reports, to poke clients.
654 */
655 public void reportActiveChange() {
Fred Quintanaac9385e2009-06-22 18:00:59 -0700656 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700657 }
658
659 /**
660 * Note that sync has started for the given account and authority.
661 */
Dianne Hackborn7a135592009-05-06 00:28:37 -0700662 public long insertStartSyncEvent(Account accountName, String authorityName,
Dianne Hackborn231cc602009-04-27 17:10:36 -0700663 long now, int source) {
664 long id;
665 synchronized (mAuthorities) {
666 if (DEBUG) Log.v(TAG, "insertStartSyncEvent: account=" + accountName
667 + " auth=" + authorityName + " source=" + source);
668 AuthorityInfo authority = getAuthorityLocked(accountName, authorityName,
669 "insertStartSyncEvent");
670 if (authority == null) {
671 return -1;
672 }
673 SyncHistoryItem item = new SyncHistoryItem();
674 item.authorityId = authority.ident;
675 item.historyId = mNextHistoryId++;
676 if (mNextHistoryId < 0) mNextHistoryId = 0;
677 item.eventTime = now;
678 item.source = source;
679 item.event = EVENT_START;
680 mSyncHistory.add(0, item);
681 while (mSyncHistory.size() > MAX_HISTORY) {
682 mSyncHistory.remove(mSyncHistory.size()-1);
683 }
684 id = item.historyId;
685 if (DEBUG) Log.v(TAG, "returning historyId " + id);
686 }
687
Fred Quintanaac9385e2009-06-22 18:00:59 -0700688 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700689 return id;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800690 }
691
692 public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage,
693 long downstreamActivity, long upstreamActivity) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700694 synchronized (mAuthorities) {
695 if (DEBUG) Log.v(TAG, "stopSyncEvent: historyId=" + historyId);
696 SyncHistoryItem item = null;
697 int i = mSyncHistory.size();
698 while (i > 0) {
699 i--;
700 item = mSyncHistory.get(i);
701 if (item.historyId == historyId) {
702 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800703 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700704 item = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800705 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700706
707 if (item == null) {
708 Log.w(TAG, "stopSyncEvent: no history for id " + historyId);
709 return;
710 }
711
712 item.elapsedTime = elapsedTime;
713 item.event = EVENT_STOP;
714 item.mesg = resultMessage;
715 item.downstreamActivity = downstreamActivity;
716 item.upstreamActivity = upstreamActivity;
717
718 SyncStatusInfo status = getOrCreateSyncStatusLocked(item.authorityId);
719
720 status.numSyncs++;
721 status.totalElapsedTime += elapsedTime;
722 switch (item.source) {
723 case SOURCE_LOCAL:
724 status.numSourceLocal++;
725 break;
726 case SOURCE_POLL:
727 status.numSourcePoll++;
728 break;
729 case SOURCE_USER:
730 status.numSourceUser++;
731 break;
732 case SOURCE_SERVER:
733 status.numSourceServer++;
734 break;
735 }
736
737 boolean writeStatisticsNow = false;
Dianne Hackborn55280a92009-05-07 15:53:46 -0700738 int day = getCurrentDayLocked();
Dianne Hackborn231cc602009-04-27 17:10:36 -0700739 if (mDayStats[0] == null) {
740 mDayStats[0] = new DayStats(day);
741 } else if (day != mDayStats[0].day) {
742 System.arraycopy(mDayStats, 0, mDayStats, 1, mDayStats.length-1);
743 mDayStats[0] = new DayStats(day);
744 writeStatisticsNow = true;
745 } else if (mDayStats[0] == null) {
746 }
747 final DayStats ds = mDayStats[0];
748
749 final long lastSyncTime = (item.eventTime + elapsedTime);
750 boolean writeStatusNow = false;
751 if (MESG_SUCCESS.equals(resultMessage)) {
752 // - if successful, update the successful columns
753 if (status.lastSuccessTime == 0 || status.lastFailureTime != 0) {
754 writeStatusNow = true;
755 }
756 status.lastSuccessTime = lastSyncTime;
757 status.lastSuccessSource = item.source;
758 status.lastFailureTime = 0;
759 status.lastFailureSource = -1;
760 status.lastFailureMesg = null;
761 status.initialFailureTime = 0;
762 ds.successCount++;
763 ds.successTime += elapsedTime;
764 } else if (!MESG_CANCELED.equals(resultMessage)) {
765 if (status.lastFailureTime == 0) {
766 writeStatusNow = true;
767 }
768 status.lastFailureTime = lastSyncTime;
769 status.lastFailureSource = item.source;
770 status.lastFailureMesg = resultMessage;
771 if (status.initialFailureTime == 0) {
772 status.initialFailureTime = lastSyncTime;
773 }
774 ds.failureCount++;
775 ds.failureTime += elapsedTime;
776 }
777
778 if (writeStatusNow) {
779 writeStatusLocked();
780 } else if (!hasMessages(MSG_WRITE_STATUS)) {
781 sendMessageDelayed(obtainMessage(MSG_WRITE_STATUS),
782 WRITE_STATUS_DELAY);
783 }
784 if (writeStatisticsNow) {
785 writeStatisticsLocked();
786 } else if (!hasMessages(MSG_WRITE_STATISTICS)) {
787 sendMessageDelayed(obtainMessage(MSG_WRITE_STATISTICS),
788 WRITE_STATISTICS_DELAY);
789 }
790 }
791
Fred Quintanaac9385e2009-06-22 18:00:59 -0700792 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700793 }
794
795 /**
796 * Return the currently active sync information, or null if there is no
797 * active sync. Note that the returned object is the real, live active
798 * sync object, so be careful what you do with it.
799 */
800 public ActiveSyncInfo getActiveSync() {
801 synchronized (mAuthorities) {
802 return mActiveSync;
803 }
804 }
805
806 /**
807 * Return an array of the current sync status for all authorities. Note
808 * that the objects inside the array are the real, live status objects,
809 * so be careful what you do with them.
810 */
811 public ArrayList<SyncStatusInfo> getSyncStatus() {
812 synchronized (mAuthorities) {
813 final int N = mSyncStatus.size();
814 ArrayList<SyncStatusInfo> ops = new ArrayList<SyncStatusInfo>(N);
815 for (int i=0; i<N; i++) {
816 ops.add(mSyncStatus.valueAt(i));
817 }
818 return ops;
819 }
820 }
821
822 /**
823 * Returns the status that matches the authority. If there are multiples accounts for
824 * the authority, the one with the latest "lastSuccessTime" status is returned.
825 * @param authority the authority whose row should be selected
826 * @return the SyncStatusInfo for the authority, or null if none exists
827 */
828 public SyncStatusInfo getStatusByAuthority(String authority) {
829 synchronized (mAuthorities) {
830 SyncStatusInfo best = null;
831 final int N = mSyncStatus.size();
832 for (int i=0; i<N; i++) {
833 SyncStatusInfo cur = mSyncStatus.get(i);
834 AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
835 if (ainfo != null && ainfo.authority.equals(authority)) {
836 if (best == null) {
837 best = cur;
838 } else if (best.lastSuccessTime > cur.lastSuccessTime) {
839 best = cur;
840 }
841 }
842 }
843 return best;
844 }
845 }
846
847 /**
848 * Return true if the pending status is true of any matching authorities.
849 */
Fred Quintanaac9385e2009-06-22 18:00:59 -0700850 public boolean isSyncPending(Account account, String authority) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700851 synchronized (mAuthorities) {
852 final int N = mSyncStatus.size();
853 for (int i=0; i<N; i++) {
854 SyncStatusInfo cur = mSyncStatus.get(i);
855 AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
856 if (ainfo == null) {
857 continue;
858 }
859 if (account != null && !ainfo.account.equals(account)) {
860 continue;
861 }
862 if (ainfo.authority.equals(authority) && cur.pending) {
863 return true;
864 }
865 }
866 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800867 }
868 }
869
870 /**
Dianne Hackborn231cc602009-04-27 17:10:36 -0700871 * Return an array of the current sync status for all authorities. Note
872 * that the objects inside the array are the real, live status objects,
873 * so be careful what you do with them.
874 */
875 public ArrayList<SyncHistoryItem> getSyncHistory() {
876 synchronized (mAuthorities) {
877 final int N = mSyncHistory.size();
878 ArrayList<SyncHistoryItem> items = new ArrayList<SyncHistoryItem>(N);
879 for (int i=0; i<N; i++) {
880 items.add(mSyncHistory.get(i));
881 }
882 return items;
883 }
884 }
885
886 /**
887 * Return an array of the current per-day statistics. Note
888 * that the objects inside the array are the real, live status objects,
889 * so be careful what you do with them.
890 */
891 public DayStats[] getDayStatistics() {
892 synchronized (mAuthorities) {
893 DayStats[] ds = new DayStats[mDayStats.length];
894 System.arraycopy(mDayStats, 0, ds, 0, ds.length);
895 return ds;
896 }
897 }
898
899 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800900 * If sync is failing for any of the provider/accounts then determine the time at which it
901 * started failing and return the earliest time over all the provider/accounts. If none are
902 * failing then return 0.
903 */
904 public long getInitialSyncFailureTime() {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700905 synchronized (mAuthorities) {
Fred Quintanaac9385e2009-06-22 18:00:59 -0700906 if (!mMasterSyncAutomatically) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700907 return 0;
908 }
909
910 long oldest = 0;
911 int i = mSyncStatus.size();
912 while (i > 0) {
913 i--;
914 SyncStatusInfo stats = mSyncStatus.valueAt(i);
915 AuthorityInfo authority = mAuthorities.get(stats.authorityId);
916 if (authority != null && authority.enabled) {
917 if (oldest == 0 || stats.initialFailureTime < oldest) {
918 oldest = stats.initialFailureTime;
919 }
920 }
921 }
922
923 return oldest;
924 }
925 }
926
Dianne Hackborn55280a92009-05-07 15:53:46 -0700927 private int getCurrentDayLocked() {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700928 mCal.setTimeInMillis(System.currentTimeMillis());
929 final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR);
930 if (mYear != mCal.get(Calendar.YEAR)) {
931 mYear = mCal.get(Calendar.YEAR);
932 mCal.clear();
933 mCal.set(Calendar.YEAR, mYear);
934 mYearInDays = (int)(mCal.getTimeInMillis()/86400000);
935 }
936 return dayOfYear + mYearInDays;
937 }
938
939 /**
940 * Retrieve an authority, returning null if one does not exist.
941 *
942 * @param accountName The name of the account for the authority.
943 * @param authorityName The name of the authority itself.
944 * @param tag If non-null, this will be used in a log message if the
945 * requested authority does not exist.
946 */
Dianne Hackborn7a135592009-05-06 00:28:37 -0700947 private AuthorityInfo getAuthorityLocked(Account accountName, String authorityName,
Dianne Hackborn231cc602009-04-27 17:10:36 -0700948 String tag) {
949 AccountInfo account = mAccounts.get(accountName);
950 if (account == null) {
951 if (tag != null) {
952 Log.w(TAG, tag + ": unknown account " + accountName);
953 }
954 return null;
955 }
956 AuthorityInfo authority = account.authorities.get(authorityName);
957 if (authority == null) {
958 if (tag != null) {
959 Log.w(TAG, tag + ": unknown authority " + authorityName);
960 }
961 return null;
962 }
963
964 return authority;
965 }
966
Dianne Hackborn7a135592009-05-06 00:28:37 -0700967 private AuthorityInfo getOrCreateAuthorityLocked(Account accountName,
Dianne Hackborn231cc602009-04-27 17:10:36 -0700968 String authorityName, int ident, boolean doWrite) {
969 AccountInfo account = mAccounts.get(accountName);
970 if (account == null) {
971 account = new AccountInfo(accountName);
972 mAccounts.put(accountName, account);
973 }
974 AuthorityInfo authority = account.authorities.get(authorityName);
975 if (authority == null) {
976 if (ident < 0) {
977 // Look for a new identifier for this authority.
978 final int N = mAuthorities.size();
979 ident = 0;
980 for (int i=0; i<N; i++) {
981 if (mAuthorities.valueAt(i).ident > ident) {
982 break;
983 }
984 ident++;
985 }
986 }
987 authority = new AuthorityInfo(accountName, authorityName, ident);
988 account.authorities.put(authorityName, authority);
989 mAuthorities.put(ident, authority);
990 if (doWrite) {
991 writeAccountInfoLocked();
992 }
993 }
994
995 return authority;
996 }
997
998 private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) {
999 SyncStatusInfo status = mSyncStatus.get(authorityId);
1000 if (status == null) {
1001 status = new SyncStatusInfo(authorityId);
1002 mSyncStatus.put(authorityId, status);
1003 }
1004 return status;
1005 }
1006
Dianne Hackborn55280a92009-05-07 15:53:46 -07001007 public void writeAllState() {
1008 synchronized (mAuthorities) {
1009 // Account info is always written so no need to do it here.
1010
1011 if (mNumPendingFinished > 0) {
1012 // Only write these if they are out of date.
1013 writePendingOperationsLocked();
1014 }
1015
1016 // Just always write these... they are likely out of date.
1017 writeStatusLocked();
1018 writeStatisticsLocked();
1019 }
1020 }
1021
Dianne Hackborn231cc602009-04-27 17:10:36 -07001022 /**
1023 * Read all account information back in to the initial engine state.
1024 */
1025 private void readAccountInfoLocked() {
1026 FileInputStream fis = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001027 try {
Dianne Hackborn231cc602009-04-27 17:10:36 -07001028 fis = mAccountInfoFile.openRead();
1029 if (DEBUG_FILE) Log.v(TAG, "Reading " + mAccountInfoFile.getBaseFile());
1030 XmlPullParser parser = Xml.newPullParser();
1031 parser.setInput(fis, null);
1032 int eventType = parser.getEventType();
1033 while (eventType != XmlPullParser.START_TAG) {
1034 eventType = parser.next();
1035 }
1036 String tagName = parser.getName();
1037 if ("accounts".equals(tagName)) {
1038 String listen = parser.getAttributeValue(
1039 null, "listen-for-tickles");
Fred Quintanaac9385e2009-06-22 18:00:59 -07001040 mMasterSyncAutomatically = listen == null
Dianne Hackborn231cc602009-04-27 17:10:36 -07001041 || Boolean.parseBoolean(listen);
1042 eventType = parser.next();
1043 do {
1044 if (eventType == XmlPullParser.START_TAG
1045 && parser.getDepth() == 2) {
1046 tagName = parser.getName();
1047 if ("authority".equals(tagName)) {
1048 int id = -1;
1049 try {
1050 id = Integer.parseInt(parser.getAttributeValue(
1051 null, "id"));
1052 } catch (NumberFormatException e) {
1053 } catch (NullPointerException e) {
1054 }
1055 if (id >= 0) {
1056 String accountName = parser.getAttributeValue(
1057 null, "account");
Dianne Hackborn7a135592009-05-06 00:28:37 -07001058 String accountType = parser.getAttributeValue(
1059 null, "type");
1060 if (accountType == null) {
1061 accountType = "com.google.GAIA";
1062 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001063 String authorityName = parser.getAttributeValue(
1064 null, "authority");
1065 String enabled = parser.getAttributeValue(
1066 null, "enabled");
1067 AuthorityInfo authority = mAuthorities.get(id);
1068 if (DEBUG_FILE) Log.v(TAG, "Adding authority: account="
1069 + accountName + " auth=" + authorityName
1070 + " enabled=" + enabled);
1071 if (authority == null) {
1072 if (DEBUG_FILE) Log.v(TAG, "Creating entry");
1073 authority = getOrCreateAuthorityLocked(
Dianne Hackborn7a135592009-05-06 00:28:37 -07001074 new Account(accountName, accountType),
1075 authorityName, id, false);
Dianne Hackborn231cc602009-04-27 17:10:36 -07001076 }
1077 if (authority != null) {
1078 authority.enabled = enabled == null
1079 || Boolean.parseBoolean(enabled);
1080 } else {
1081 Log.w(TAG, "Failure adding authority: account="
1082 + accountName + " auth=" + authorityName
1083 + " enabled=" + enabled);
1084 }
1085 }
1086 }
1087 }
1088 eventType = parser.next();
1089 } while (eventType != XmlPullParser.END_DOCUMENT);
1090 }
1091 } catch (XmlPullParserException e) {
1092 Log.w(TAG, "Error reading accounts", e);
1093 } catch (java.io.IOException e) {
1094 if (fis == null) Log.i(TAG, "No initial accounts");
1095 else Log.w(TAG, "Error reading accounts", e);
1096 } finally {
1097 if (fis != null) {
1098 try {
1099 fis.close();
1100 } catch (java.io.IOException e1) {
1101 }
1102 }
1103 }
1104 }
1105
1106 /**
1107 * Write all account information to the account file.
1108 */
1109 private void writeAccountInfoLocked() {
1110 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mAccountInfoFile.getBaseFile());
1111 FileOutputStream fos = null;
1112
1113 try {
1114 fos = mAccountInfoFile.startWrite();
1115 XmlSerializer out = new FastXmlSerializer();
1116 out.setOutput(fos, "utf-8");
1117 out.startDocument(null, true);
1118 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
1119
1120 out.startTag(null, "accounts");
Fred Quintanaac9385e2009-06-22 18:00:59 -07001121 if (!mMasterSyncAutomatically) {
Dianne Hackborn231cc602009-04-27 17:10:36 -07001122 out.attribute(null, "listen-for-tickles", "false");
1123 }
1124
1125 final int N = mAuthorities.size();
1126 for (int i=0; i<N; i++) {
1127 AuthorityInfo authority = mAuthorities.get(i);
1128 out.startTag(null, "authority");
1129 out.attribute(null, "id", Integer.toString(authority.ident));
Dianne Hackborn7a135592009-05-06 00:28:37 -07001130 out.attribute(null, "account", authority.account.mName);
1131 out.attribute(null, "type", authority.account.mType);
Dianne Hackborn231cc602009-04-27 17:10:36 -07001132 out.attribute(null, "authority", authority.authority);
1133 if (!authority.enabled) {
1134 out.attribute(null, "enabled", "false");
1135 }
1136 out.endTag(null, "authority");
1137 }
1138
1139 out.endTag(null, "accounts");
1140
1141 out.endDocument();
1142
1143 mAccountInfoFile.finishWrite(fos);
1144 } catch (java.io.IOException e1) {
1145 Log.w(TAG, "Error writing accounts", e1);
1146 if (fos != null) {
1147 mAccountInfoFile.failWrite(fos);
1148 }
1149 }
1150 }
1151
1152 static int getIntColumn(Cursor c, String name) {
1153 return c.getInt(c.getColumnIndex(name));
1154 }
1155
1156 static long getLongColumn(Cursor c, String name) {
1157 return c.getLong(c.getColumnIndex(name));
1158 }
1159
1160 /**
1161 * Load sync engine state from the old syncmanager database, and then
1162 * erase it. Note that we don't deal with pending operations, active
1163 * sync, or history.
1164 */
1165 private void readLegacyAccountInfoLocked() {
1166 // Look for old database to initialize from.
1167 File file = mContext.getDatabasePath("syncmanager.db");
1168 if (!file.exists()) {
1169 return;
1170 }
1171 String path = file.getPath();
1172 SQLiteDatabase db = null;
1173 try {
1174 db = SQLiteDatabase.openDatabase(path, null,
1175 SQLiteDatabase.OPEN_READONLY);
1176 } catch (SQLiteException e) {
1177 }
1178
1179 if (db != null) {
Dianne Hackborn2d5ed1f2009-05-06 15:22:38 -07001180 final boolean hasType = db.getVersion() >= 11;
1181
Dianne Hackborn231cc602009-04-27 17:10:36 -07001182 // Copy in all of the status information, as well as accounts.
1183 if (DEBUG_FILE) Log.v(TAG, "Reading legacy sync accounts db");
1184 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
1185 qb.setTables("stats, status");
1186 HashMap<String,String> map = new HashMap<String,String>();
1187 map.put("_id", "status._id as _id");
1188 map.put("account", "stats.account as account");
Dianne Hackborn2d5ed1f2009-05-06 15:22:38 -07001189 if (hasType) {
1190 map.put("account_type", "stats.account_type as account_type");
1191 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001192 map.put("authority", "stats.authority as authority");
1193 map.put("totalElapsedTime", "totalElapsedTime");
1194 map.put("numSyncs", "numSyncs");
1195 map.put("numSourceLocal", "numSourceLocal");
1196 map.put("numSourcePoll", "numSourcePoll");
1197 map.put("numSourceServer", "numSourceServer");
1198 map.put("numSourceUser", "numSourceUser");
1199 map.put("lastSuccessSource", "lastSuccessSource");
1200 map.put("lastSuccessTime", "lastSuccessTime");
1201 map.put("lastFailureSource", "lastFailureSource");
1202 map.put("lastFailureTime", "lastFailureTime");
1203 map.put("lastFailureMesg", "lastFailureMesg");
1204 map.put("pending", "pending");
1205 qb.setProjectionMap(map);
1206 qb.appendWhere("stats._id = status.stats_id");
1207 Cursor c = qb.query(db, null, null, null, null, null, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001208 while (c.moveToNext()) {
Dianne Hackborn231cc602009-04-27 17:10:36 -07001209 String accountName = c.getString(c.getColumnIndex("account"));
Dianne Hackborn2d5ed1f2009-05-06 15:22:38 -07001210 String accountType = hasType
1211 ? c.getString(c.getColumnIndex("account_type")) : null;
Dianne Hackborn7a135592009-05-06 00:28:37 -07001212 if (accountType == null) {
1213 accountType = "com.google.GAIA";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001214 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001215 String authorityName = c.getString(c.getColumnIndex("authority"));
1216 AuthorityInfo authority = this.getOrCreateAuthorityLocked(
Dianne Hackborn7a135592009-05-06 00:28:37 -07001217 new Account(accountName, accountType),
1218 authorityName, -1, false);
Dianne Hackborn231cc602009-04-27 17:10:36 -07001219 if (authority != null) {
1220 int i = mSyncStatus.size();
1221 boolean found = false;
1222 SyncStatusInfo st = null;
1223 while (i > 0) {
1224 i--;
1225 st = mSyncStatus.get(i);
1226 if (st.authorityId == authority.ident) {
1227 found = true;
1228 break;
1229 }
1230 }
1231 if (!found) {
1232 st = new SyncStatusInfo(authority.ident);
1233 mSyncStatus.put(authority.ident, st);
1234 }
1235 st.totalElapsedTime = getLongColumn(c, "totalElapsedTime");
1236 st.numSyncs = getIntColumn(c, "numSyncs");
1237 st.numSourceLocal = getIntColumn(c, "numSourceLocal");
1238 st.numSourcePoll = getIntColumn(c, "numSourcePoll");
1239 st.numSourceServer = getIntColumn(c, "numSourceServer");
1240 st.numSourceUser = getIntColumn(c, "numSourceUser");
1241 st.lastSuccessSource = getIntColumn(c, "lastSuccessSource");
1242 st.lastSuccessTime = getLongColumn(c, "lastSuccessTime");
1243 st.lastFailureSource = getIntColumn(c, "lastFailureSource");
1244 st.lastFailureTime = getLongColumn(c, "lastFailureTime");
1245 st.lastFailureMesg = c.getString(c.getColumnIndex("lastFailureMesg"));
1246 st.pending = getIntColumn(c, "pending") != 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001247 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001248 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001249
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001250 c.close();
Dianne Hackborn231cc602009-04-27 17:10:36 -07001251
1252 // Retrieve the settings.
1253 qb = new SQLiteQueryBuilder();
1254 qb.setTables("settings");
1255 c = qb.query(db, null, null, null, null, null, null);
1256 while (c.moveToNext()) {
1257 String name = c.getString(c.getColumnIndex("name"));
1258 String value = c.getString(c.getColumnIndex("value"));
1259 if (name == null) continue;
1260 if (name.equals("listen_for_tickles")) {
Fred Quintanaac9385e2009-06-22 18:00:59 -07001261 setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value));
Dianne Hackborn231cc602009-04-27 17:10:36 -07001262 } else if (name.startsWith("sync_provider_")) {
1263 String provider = name.substring("sync_provider_".length(),
1264 name.length());
Fred Quintanaac9385e2009-06-22 18:00:59 -07001265 int i = mAuthorities.size();
1266 while (i > 0) {
1267 i--;
1268 AuthorityInfo authority = mAuthorities.get(i);
1269 if (authority.authority.equals(provider)) {
1270 authority.enabled = value == null || Boolean.parseBoolean(value);
1271 }
1272 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001273 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001274 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001275
1276 c.close();
1277
1278 db.close();
1279
1280 writeAccountInfoLocked();
1281 writeStatusLocked();
1282 (new File(path)).delete();
1283 }
1284 }
1285
1286 public static final int STATUS_FILE_END = 0;
1287 public static final int STATUS_FILE_ITEM = 100;
1288
1289 /**
1290 * Read all sync status back in to the initial engine state.
1291 */
1292 private void readStatusLocked() {
1293 if (DEBUG_FILE) Log.v(TAG, "Reading " + mStatusFile.getBaseFile());
1294 try {
1295 byte[] data = mStatusFile.readFully();
1296 Parcel in = Parcel.obtain();
1297 in.unmarshall(data, 0, data.length);
1298 in.setDataPosition(0);
1299 int token;
1300 while ((token=in.readInt()) != STATUS_FILE_END) {
1301 if (token == STATUS_FILE_ITEM) {
1302 SyncStatusInfo status = new SyncStatusInfo(in);
1303 if (mAuthorities.indexOfKey(status.authorityId) >= 0) {
1304 status.pending = false;
1305 if (DEBUG_FILE) Log.v(TAG, "Adding status for id "
1306 + status.authorityId);
1307 mSyncStatus.put(status.authorityId, status);
1308 }
1309 } else {
1310 // Ooops.
1311 Log.w(TAG, "Unknown status token: " + token);
1312 break;
1313 }
1314 }
1315 } catch (java.io.IOException e) {
1316 Log.i(TAG, "No initial status");
1317 }
1318 }
1319
1320 /**
1321 * Write all sync status to the sync status file.
1322 */
1323 private void writeStatusLocked() {
1324 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatusFile.getBaseFile());
1325
1326 // The file is being written, so we don't need to have a scheduled
1327 // write until the next change.
1328 removeMessages(MSG_WRITE_STATUS);
1329
1330 FileOutputStream fos = null;
1331 try {
1332 fos = mStatusFile.startWrite();
1333 Parcel out = Parcel.obtain();
1334 final int N = mSyncStatus.size();
1335 for (int i=0; i<N; i++) {
1336 SyncStatusInfo status = mSyncStatus.valueAt(i);
1337 out.writeInt(STATUS_FILE_ITEM);
1338 status.writeToParcel(out, 0);
1339 }
1340 out.writeInt(STATUS_FILE_END);
1341 fos.write(out.marshall());
1342 out.recycle();
1343
1344 mStatusFile.finishWrite(fos);
1345 } catch (java.io.IOException e1) {
1346 Log.w(TAG, "Error writing status", e1);
1347 if (fos != null) {
1348 mStatusFile.failWrite(fos);
1349 }
1350 }
1351 }
1352
1353 public static final int PENDING_OPERATION_VERSION = 1;
1354
1355 /**
1356 * Read all pending operations back in to the initial engine state.
1357 */
1358 private void readPendingOperationsLocked() {
1359 if (DEBUG_FILE) Log.v(TAG, "Reading " + mPendingFile.getBaseFile());
1360 try {
1361 byte[] data = mPendingFile.readFully();
1362 Parcel in = Parcel.obtain();
1363 in.unmarshall(data, 0, data.length);
1364 in.setDataPosition(0);
1365 final int SIZE = in.dataSize();
1366 while (in.dataPosition() < SIZE) {
1367 int version = in.readInt();
1368 if (version != PENDING_OPERATION_VERSION) {
1369 Log.w(TAG, "Unknown pending operation version "
1370 + version + "; dropping all ops");
1371 break;
1372 }
1373 int authorityId = in.readInt();
1374 int syncSource = in.readInt();
1375 byte[] flatExtras = in.createByteArray();
1376 AuthorityInfo authority = mAuthorities.get(authorityId);
1377 if (authority != null) {
1378 Bundle extras = null;
1379 if (flatExtras != null) {
1380 extras = unflattenBundle(flatExtras);
1381 }
1382 PendingOperation op = new PendingOperation(
1383 authority.account, syncSource,
1384 authority.authority, extras);
1385 op.authorityId = authorityId;
1386 op.flatExtras = flatExtras;
1387 if (DEBUG_FILE) Log.v(TAG, "Adding pending op: account=" + op.account
1388 + " auth=" + op.authority
1389 + " src=" + op.syncSource
1390 + " extras=" + op.extras);
1391 mPendingOperations.add(op);
1392 }
1393 }
1394 } catch (java.io.IOException e) {
1395 Log.i(TAG, "No initial pending operations");
1396 }
1397 }
1398
1399 private void writePendingOperationLocked(PendingOperation op, Parcel out) {
1400 out.writeInt(PENDING_OPERATION_VERSION);
1401 out.writeInt(op.authorityId);
1402 out.writeInt(op.syncSource);
1403 if (op.flatExtras == null && op.extras != null) {
1404 op.flatExtras = flattenBundle(op.extras);
1405 }
1406 out.writeByteArray(op.flatExtras);
1407 }
1408
1409 /**
1410 * Write all currently pending ops to the pending ops file.
1411 */
1412 private void writePendingOperationsLocked() {
1413 final int N = mPendingOperations.size();
1414 FileOutputStream fos = null;
1415 try {
1416 if (N == 0) {
1417 if (DEBUG_FILE) Log.v(TAG, "Truncating " + mPendingFile.getBaseFile());
1418 mPendingFile.truncate();
1419 return;
1420 }
1421
1422 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mPendingFile.getBaseFile());
1423 fos = mPendingFile.startWrite();
1424
1425 Parcel out = Parcel.obtain();
1426 for (int i=0; i<N; i++) {
1427 PendingOperation op = mPendingOperations.get(i);
1428 writePendingOperationLocked(op, out);
1429 }
1430 fos.write(out.marshall());
1431 out.recycle();
1432
1433 mPendingFile.finishWrite(fos);
1434 } catch (java.io.IOException e1) {
1435 Log.w(TAG, "Error writing pending operations", e1);
1436 if (fos != null) {
1437 mPendingFile.failWrite(fos);
1438 }
1439 }
1440 }
1441
1442 /**
1443 * Append the given operation to the pending ops file; if unable to,
1444 * write all pending ops.
1445 */
1446 private void appendPendingOperationLocked(PendingOperation op) {
1447 if (DEBUG_FILE) Log.v(TAG, "Appending to " + mPendingFile.getBaseFile());
1448 FileOutputStream fos = null;
1449 try {
1450 fos = mPendingFile.openAppend();
1451 } catch (java.io.IOException e) {
1452 if (DEBUG_FILE) Log.v(TAG, "Failed append; writing full file");
1453 writePendingOperationsLocked();
1454 return;
1455 }
1456
1457 try {
1458 Parcel out = Parcel.obtain();
1459 writePendingOperationLocked(op, out);
1460 fos.write(out.marshall());
1461 out.recycle();
1462 } catch (java.io.IOException e1) {
1463 Log.w(TAG, "Error writing pending operations", e1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001464 } finally {
Dianne Hackborn231cc602009-04-27 17:10:36 -07001465 try {
1466 fos.close();
1467 } catch (java.io.IOException e2) {
1468 }
1469 }
1470 }
1471
1472 static private byte[] flattenBundle(Bundle bundle) {
1473 byte[] flatData = null;
1474 Parcel parcel = Parcel.obtain();
1475 try {
1476 bundle.writeToParcel(parcel, 0);
1477 flatData = parcel.marshall();
1478 } finally {
1479 parcel.recycle();
1480 }
1481 return flatData;
1482 }
1483
1484 static private Bundle unflattenBundle(byte[] flatData) {
1485 Bundle bundle;
1486 Parcel parcel = Parcel.obtain();
1487 try {
1488 parcel.unmarshall(flatData, 0, flatData.length);
1489 parcel.setDataPosition(0);
1490 bundle = parcel.readBundle();
1491 } catch (RuntimeException e) {
1492 // A RuntimeException is thrown if we were unable to parse the parcel.
1493 // Create an empty parcel in this case.
1494 bundle = new Bundle();
1495 } finally {
1496 parcel.recycle();
1497 }
1498 return bundle;
1499 }
1500
1501 public static final int STATISTICS_FILE_END = 0;
1502 public static final int STATISTICS_FILE_ITEM_OLD = 100;
1503 public static final int STATISTICS_FILE_ITEM = 101;
1504
1505 /**
1506 * Read all sync statistics back in to the initial engine state.
1507 */
1508 private void readStatisticsLocked() {
1509 try {
1510 byte[] data = mStatisticsFile.readFully();
1511 Parcel in = Parcel.obtain();
1512 in.unmarshall(data, 0, data.length);
1513 in.setDataPosition(0);
1514 int token;
1515 int index = 0;
1516 while ((token=in.readInt()) != STATISTICS_FILE_END) {
1517 if (token == STATISTICS_FILE_ITEM
1518 || token == STATISTICS_FILE_ITEM_OLD) {
1519 int day = in.readInt();
1520 if (token == STATISTICS_FILE_ITEM_OLD) {
1521 day = day - 2009 + 14245; // Magic!
1522 }
1523 DayStats ds = new DayStats(day);
1524 ds.successCount = in.readInt();
1525 ds.successTime = in.readLong();
1526 ds.failureCount = in.readInt();
1527 ds.failureTime = in.readLong();
1528 if (index < mDayStats.length) {
1529 mDayStats[index] = ds;
1530 index++;
1531 }
1532 } else {
1533 // Ooops.
1534 Log.w(TAG, "Unknown stats token: " + token);
1535 break;
1536 }
1537 }
1538 } catch (java.io.IOException e) {
1539 Log.i(TAG, "No initial statistics");
1540 }
1541 }
1542
1543 /**
1544 * Write all sync statistics to the sync status file.
1545 */
1546 private void writeStatisticsLocked() {
1547 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatisticsFile.getBaseFile());
1548
1549 // The file is being written, so we don't need to have a scheduled
1550 // write until the next change.
1551 removeMessages(MSG_WRITE_STATISTICS);
1552
1553 FileOutputStream fos = null;
1554 try {
1555 fos = mStatisticsFile.startWrite();
1556 Parcel out = Parcel.obtain();
1557 final int N = mDayStats.length;
1558 for (int i=0; i<N; i++) {
1559 DayStats ds = mDayStats[i];
1560 if (ds == null) {
1561 break;
1562 }
1563 out.writeInt(STATISTICS_FILE_ITEM);
1564 out.writeInt(ds.day);
1565 out.writeInt(ds.successCount);
1566 out.writeLong(ds.successTime);
1567 out.writeInt(ds.failureCount);
1568 out.writeLong(ds.failureTime);
1569 }
1570 out.writeInt(STATISTICS_FILE_END);
1571 fos.write(out.marshall());
1572 out.recycle();
1573
1574 mStatisticsFile.finishWrite(fos);
1575 } catch (java.io.IOException e1) {
1576 Log.w(TAG, "Error writing stats", e1);
1577 if (fos != null) {
1578 mStatisticsFile.failWrite(fos);
1579 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001580 }
1581 }
1582}