blob: 264796281283ffa21957bd82a548b8c35dc57792 [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 Quintana5e787c42009-08-16 23:13:53 -0700158 int syncable;
Fred Quintanaac9385e2009-06-22 18:00:59 -0700159
Dianne Hackborn7a135592009-05-06 00:28:37 -0700160 AuthorityInfo(Account account, String authority, int ident) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700161 this.account = account;
162 this.authority = authority;
163 this.ident = ident;
Joe Onorato8294fad2009-07-15 16:08:44 -0700164 enabled = SYNC_ENABLED_DEFAULT;
Fred Quintana5e787c42009-08-16 23:13:53 -0700165 // TODO: change the default to -1 when the syncadapters are changed to set this
166 syncable = 1;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700167 }
168 }
169
170 public static class SyncHistoryItem {
171 int authorityId;
172 int historyId;
173 long eventTime;
174 long elapsedTime;
175 int source;
176 int event;
177 long upstreamActivity;
178 long downstreamActivity;
179 String mesg;
180 }
181
182 public static class DayStats {
183 public final int day;
184 public int successCount;
185 public long successTime;
186 public int failureCount;
187 public long failureTime;
188
189 public DayStats(int day) {
190 this.day = day;
191 }
192 }
193
194 // Primary list of all syncable authorities. Also our global lock.
195 private final SparseArray<AuthorityInfo> mAuthorities =
196 new SparseArray<AuthorityInfo>();
197
Dianne Hackborn7a135592009-05-06 00:28:37 -0700198 private final HashMap<Account, AccountInfo> mAccounts =
199 new HashMap<Account, AccountInfo>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800200
Dianne Hackborn231cc602009-04-27 17:10:36 -0700201 private final ArrayList<PendingOperation> mPendingOperations =
202 new ArrayList<PendingOperation>();
203
204 private ActiveSyncInfo mActiveSync;
205
206 private final SparseArray<SyncStatusInfo> mSyncStatus =
207 new SparseArray<SyncStatusInfo>();
208
209 private final ArrayList<SyncHistoryItem> mSyncHistory =
210 new ArrayList<SyncHistoryItem>();
211
212 private final RemoteCallbackList<ISyncStatusObserver> mChangeListeners
213 = new RemoteCallbackList<ISyncStatusObserver>();
214
215 // We keep 4 weeks of stats.
216 private final DayStats[] mDayStats = new DayStats[7*4];
217 private final Calendar mCal;
218 private int mYear;
219 private int mYearInDays;
220
221 private final Context mContext;
222 private static volatile SyncStorageEngine sSyncStorageEngine = null;
223
224 /**
225 * This file contains the core engine state: all accounts and the
226 * settings for them. It must never be lost, and should be changed
227 * infrequently, so it is stored as an XML file.
228 */
229 private final AtomicFile mAccountInfoFile;
230
231 /**
232 * This file contains the current sync status. We would like to retain
233 * it across boots, but its loss is not the end of the world, so we store
234 * this information as binary data.
235 */
236 private final AtomicFile mStatusFile;
237
238 /**
239 * This file contains sync statistics. This is purely debugging information
240 * so is written infrequently and can be thrown away at any time.
241 */
242 private final AtomicFile mStatisticsFile;
243
244 /**
245 * This file contains the pending sync operations. It is a binary file,
246 * which must be updated every time an operation is added or removed,
247 * so we have special handling of it.
248 */
249 private final AtomicFile mPendingFile;
250 private static final int PENDING_FINISH_TO_WRITE = 4;
251 private int mNumPendingFinished = 0;
252
253 private int mNextHistoryId = 0;
Fred Quintanaac9385e2009-06-22 18:00:59 -0700254 private boolean mMasterSyncAutomatically = true;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700255
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800256 private SyncStorageEngine(Context context) {
257 mContext = context;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800258 sSyncStorageEngine = this;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700259
260 mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
261
262 File dataDir = Environment.getDataDirectory();
263 File systemDir = new File(dataDir, "system");
264 File syncDir = new File(systemDir, "sync");
265 mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
266 mStatusFile = new AtomicFile(new File(syncDir, "status.bin"));
267 mPendingFile = new AtomicFile(new File(syncDir, "pending.bin"));
268 mStatisticsFile = new AtomicFile(new File(syncDir, "stats.bin"));
269
270 readAccountInfoLocked();
271 readStatusLocked();
272 readPendingOperationsLocked();
273 readStatisticsLocked();
274 readLegacyAccountInfoLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800275 }
276
277 public static SyncStorageEngine newTestInstance(Context context) {
278 return new SyncStorageEngine(context);
279 }
280
281 public static void init(Context context) {
282 if (sSyncStorageEngine != null) {
283 throw new IllegalStateException("already initialized");
284 }
285 sSyncStorageEngine = new SyncStorageEngine(context);
286 }
287
288 public static SyncStorageEngine getSingleton() {
289 if (sSyncStorageEngine == null) {
290 throw new IllegalStateException("not initialized");
291 }
292 return sSyncStorageEngine;
293 }
294
Dianne Hackborn231cc602009-04-27 17:10:36 -0700295 @Override public void handleMessage(Message msg) {
296 if (msg.what == MSG_WRITE_STATUS) {
297 synchronized (mAccounts) {
298 writeStatusLocked();
Fred Quintanad9d2f112009-04-23 13:36:27 -0700299 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700300 } else if (msg.what == MSG_WRITE_STATISTICS) {
301 synchronized (mAccounts) {
302 writeStatisticsLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800303 }
304 }
305 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700306
307 public void addStatusChangeListener(int mask, ISyncStatusObserver callback) {
308 synchronized (mAuthorities) {
309 mChangeListeners.register(callback, mask);
310 }
311 }
312
313 public void removeStatusChangeListener(ISyncStatusObserver callback) {
314 synchronized (mAuthorities) {
315 mChangeListeners.unregister(callback);
316 }
317 }
318
319 private void reportChange(int which) {
320 ArrayList<ISyncStatusObserver> reports = null;
321 synchronized (mAuthorities) {
322 int i = mChangeListeners.beginBroadcast();
323 while (i > 0) {
324 i--;
325 Integer mask = (Integer)mChangeListeners.getBroadcastCookie(i);
326 if ((which & mask.intValue()) == 0) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800327 continue;
328 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700329 if (reports == null) {
330 reports = new ArrayList<ISyncStatusObserver>(i);
331 }
332 reports.add(mChangeListeners.getBroadcastItem(i));
333 }
Dianne Hackbornb06ea702009-07-13 13:07:51 -0700334 mChangeListeners.finishBroadcast();
Dianne Hackborn231cc602009-04-27 17:10:36 -0700335 }
336
337 if (DEBUG) Log.v(TAG, "reportChange " + which + " to: " + reports);
338
339 if (reports != null) {
340 int i = reports.size();
341 while (i > 0) {
342 i--;
343 try {
344 reports.get(i).onStatusChanged(which);
345 } catch (RemoteException e) {
346 // The remote callback list will take care of this for us.
347 }
348 }
349 }
Amith Yamasani70c874b2009-07-06 14:53:25 -0700350 // Inform the backup manager about a data change
351 IBackupManager ibm = IBackupManager.Stub.asInterface(
352 ServiceManager.getService(Context.BACKUP_SERVICE));
353 if (ibm != null) {
354 try {
355 ibm.dataChanged("com.android.providers.settings");
356 } catch (RemoteException e) {
357 // Try again later
358 }
359 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700360 }
Amith Yamasani70c874b2009-07-06 14:53:25 -0700361
Fred Quintanaac9385e2009-06-22 18:00:59 -0700362 public boolean getSyncAutomatically(Account account, String providerName) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700363 synchronized (mAuthorities) {
364 if (account != null) {
365 AuthorityInfo authority = getAuthorityLocked(account, providerName,
Fred Quintanaac9385e2009-06-22 18:00:59 -0700366 "getSyncAutomatically");
367 return authority != null && authority.enabled;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700368 }
Fred Quintanaac9385e2009-06-22 18:00:59 -0700369
Dianne Hackborn231cc602009-04-27 17:10:36 -0700370 int i = mAuthorities.size();
371 while (i > 0) {
372 i--;
373 AuthorityInfo authority = mAuthorities.get(i);
374 if (authority.authority.equals(providerName)
375 && authority.enabled) {
376 return true;
377 }
378 }
379 return false;
380 }
381 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800382
Fred Quintanaac9385e2009-06-22 18:00:59 -0700383 public void setSyncAutomatically(Account account, String providerName, boolean sync) {
Joe Onorato8294fad2009-07-15 16:08:44 -0700384 boolean wasEnabled;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700385 synchronized (mAuthorities) {
Joe Onorato8294fad2009-07-15 16:08:44 -0700386 AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false);
387 wasEnabled = authority.enabled;
388 authority.enabled = sync;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700389 writeAccountInfoLocked();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800390 }
Joe Onorato8294fad2009-07-15 16:08:44 -0700391
392 if (!wasEnabled && sync) {
393 mContext.getContentResolver().requestSync(account, providerName, new Bundle());
394 }
Fred Quintanaac9385e2009-06-22 18:00:59 -0700395 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800396 }
397
Fred Quintana5e787c42009-08-16 23:13:53 -0700398 public int getIsSyncable(Account account, String providerName) {
399 synchronized (mAuthorities) {
400 if (account != null) {
401 AuthorityInfo authority = getAuthorityLocked(account, providerName,
402 "getIsSyncable");
403 if (authority == null) {
404 return -1;
405 }
406 return authority.syncable;
407 }
408
409 int i = mAuthorities.size();
410 while (i > 0) {
411 i--;
412 AuthorityInfo authority = mAuthorities.get(i);
413 if (authority.authority.equals(providerName)) {
414 return authority.syncable;
415 }
416 }
417 return -1;
418 }
419 }
420
421 public void setIsSyncable(Account account, String providerName, int syncable) {
422 int oldState;
423 synchronized (mAuthorities) {
424 AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false);
425 oldState = authority.syncable;
426 authority.syncable = syncable;
427 writeAccountInfoLocked();
428 }
429
430 if (oldState <= 0 && syncable > 0) {
431 mContext.getContentResolver().requestSync(account, providerName, new Bundle());
432 }
433 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
434 }
435
Fred Quintanaac9385e2009-06-22 18:00:59 -0700436 public void setMasterSyncAutomatically(boolean flag) {
Joe Onorato8294fad2009-07-15 16:08:44 -0700437 boolean old;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700438 synchronized (mAuthorities) {
Joe Onorato8294fad2009-07-15 16:08:44 -0700439 old = mMasterSyncAutomatically;
Fred Quintanaac9385e2009-06-22 18:00:59 -0700440 mMasterSyncAutomatically = flag;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700441 writeAccountInfoLocked();
442 }
Joe Onorato8294fad2009-07-15 16:08:44 -0700443 if (!old && flag) {
444 mContext.getContentResolver().requestSync(null, null, new Bundle());
445 }
Fred Quintanaac9385e2009-06-22 18:00:59 -0700446 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
447 mContext.sendBroadcast(SYNC_CONNECTION_SETTING_CHANGED_INTENT);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700448 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800449
Fred Quintanaac9385e2009-06-22 18:00:59 -0700450 public boolean getMasterSyncAutomatically() {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700451 synchronized (mAuthorities) {
Fred Quintanaac9385e2009-06-22 18:00:59 -0700452 return mMasterSyncAutomatically;
Dianne Hackborn231cc602009-04-27 17:10:36 -0700453 }
454 }
455
Dianne Hackborn7a135592009-05-06 00:28:37 -0700456 public AuthorityInfo getAuthority(Account account, String authority) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700457 synchronized (mAuthorities) {
458 return getAuthorityLocked(account, authority, null);
459 }
460 }
461
462 public AuthorityInfo getAuthority(int authorityId) {
463 synchronized (mAuthorities) {
464 return mAuthorities.get(authorityId);
465 }
466 }
467
468 /**
469 * Returns true if there is currently a sync operation for the given
470 * account or authority in the pending list, or actively being processed.
471 */
Dianne Hackborn7a135592009-05-06 00:28:37 -0700472 public boolean isSyncActive(Account account, String authority) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700473 synchronized (mAuthorities) {
474 int i = mPendingOperations.size();
475 while (i > 0) {
476 i--;
477 // TODO(fredq): this probably shouldn't be considering
478 // pending operations.
479 PendingOperation op = mPendingOperations.get(i);
480 if (op.account.equals(account) && op.authority.equals(authority)) {
481 return true;
482 }
483 }
484
485 if (mActiveSync != null) {
486 AuthorityInfo ainfo = getAuthority(mActiveSync.authorityId);
487 if (ainfo != null && ainfo.account.equals(account)
488 && ainfo.authority.equals(authority)) {
489 return true;
490 }
491 }
492 }
493
494 return false;
495 }
496
497 public PendingOperation insertIntoPending(PendingOperation op) {
498 synchronized (mAuthorities) {
499 if (DEBUG) Log.v(TAG, "insertIntoPending: account=" + op.account
500 + " auth=" + op.authority
501 + " src=" + op.syncSource
502 + " extras=" + op.extras);
503
504 AuthorityInfo authority = getOrCreateAuthorityLocked(op.account,
505 op.authority,
506 -1 /* desired identifier */,
507 true /* write accounts to storage */);
508 if (authority == null) {
509 return null;
510 }
511
512 op = new PendingOperation(op);
513 op.authorityId = authority.ident;
514 mPendingOperations.add(op);
515 appendPendingOperationLocked(op);
516
517 SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
518 status.pending = true;
519 }
520
Fred Quintanaac9385e2009-06-22 18:00:59 -0700521 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700522 return op;
523 }
524
525 public boolean deleteFromPending(PendingOperation op) {
526 boolean res = false;
527 synchronized (mAuthorities) {
528 if (DEBUG) Log.v(TAG, "deleteFromPending: account=" + op.account
529 + " auth=" + op.authority
530 + " src=" + op.syncSource
531 + " extras=" + op.extras);
532 if (mPendingOperations.remove(op)) {
533 if (mPendingOperations.size() == 0
534 || mNumPendingFinished >= PENDING_FINISH_TO_WRITE) {
535 writePendingOperationsLocked();
536 mNumPendingFinished = 0;
537 } else {
538 mNumPendingFinished++;
539 }
540
541 AuthorityInfo authority = getAuthorityLocked(op.account, op.authority,
542 "deleteFromPending");
543 if (authority != null) {
544 if (DEBUG) Log.v(TAG, "removing - " + authority);
545 final int N = mPendingOperations.size();
546 boolean morePending = false;
547 for (int i=0; i<N; i++) {
548 PendingOperation cur = mPendingOperations.get(i);
549 if (cur.account.equals(op.account)
550 && cur.authority.equals(op.authority)) {
551 morePending = true;
552 break;
553 }
554 }
555
556 if (!morePending) {
557 if (DEBUG) Log.v(TAG, "no more pending!");
558 SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
559 status.pending = false;
560 }
561 }
562
563 res = true;
564 }
565 }
566
Fred Quintanaac9385e2009-06-22 18:00:59 -0700567 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700568 return res;
569 }
570
571 public int clearPending() {
572 int num;
573 synchronized (mAuthorities) {
574 if (DEBUG) Log.v(TAG, "clearPending");
575 num = mPendingOperations.size();
576 mPendingOperations.clear();
577 final int N = mSyncStatus.size();
578 for (int i=0; i<N; i++) {
579 mSyncStatus.get(i).pending = false;
580 }
581 writePendingOperationsLocked();
582 }
Fred Quintanaac9385e2009-06-22 18:00:59 -0700583 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_PENDING);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700584 return num;
585 }
586
587 /**
588 * Return a copy of the current array of pending operations. The
589 * PendingOperation objects are the real objects stored inside, so that
590 * they can be used with deleteFromPending().
591 */
592 public ArrayList<PendingOperation> getPendingOperations() {
593 synchronized (mAuthorities) {
594 return new ArrayList<PendingOperation>(mPendingOperations);
595 }
596 }
597
598 /**
599 * Return the number of currently pending operations.
600 */
601 public int getPendingOperationCount() {
602 synchronized (mAuthorities) {
603 return mPendingOperations.size();
604 }
605 }
606
607 /**
608 * Called when the set of account has changed, given the new array of
609 * active accounts.
610 */
Dianne Hackborn7a135592009-05-06 00:28:37 -0700611 public void doDatabaseCleanup(Account[] accounts) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700612 synchronized (mAuthorities) {
613 if (DEBUG) Log.w(TAG, "Updating for new accounts...");
614 SparseArray<AuthorityInfo> removing = new SparseArray<AuthorityInfo>();
615 Iterator<AccountInfo> accIt = mAccounts.values().iterator();
616 while (accIt.hasNext()) {
617 AccountInfo acc = accIt.next();
618 if (!ArrayUtils.contains(accounts, acc.account)) {
619 // This account no longer exists...
620 if (DEBUG) Log.w(TAG, "Account removed: " + acc.account);
621 for (AuthorityInfo auth : acc.authorities.values()) {
622 removing.put(auth.ident, auth);
623 }
624 accIt.remove();
625 }
626 }
627
628 // Clean out all data structures.
629 int i = removing.size();
630 if (i > 0) {
631 while (i > 0) {
632 i--;
633 int ident = removing.keyAt(i);
634 mAuthorities.remove(ident);
635 int j = mSyncStatus.size();
636 while (j > 0) {
637 j--;
638 if (mSyncStatus.keyAt(j) == ident) {
639 mSyncStatus.remove(mSyncStatus.keyAt(j));
640 }
641 }
642 j = mSyncHistory.size();
643 while (j > 0) {
644 j--;
645 if (mSyncHistory.get(j).authorityId == ident) {
646 mSyncHistory.remove(j);
647 }
648 }
649 }
650 writeAccountInfoLocked();
651 writeStatusLocked();
652 writePendingOperationsLocked();
653 writeStatisticsLocked();
654 }
655 }
656 }
657
658 /**
659 * Called when the currently active sync is changing (there can only be
660 * one at a time). Either supply a valid ActiveSyncContext with information
661 * about the sync, or null to stop the currently active sync.
662 */
663 public void setActiveSync(SyncManager.ActiveSyncContext activeSyncContext) {
664 synchronized (mAuthorities) {
665 if (activeSyncContext != null) {
666 if (DEBUG) Log.v(TAG, "setActiveSync: account="
667 + activeSyncContext.mSyncOperation.account
668 + " auth=" + activeSyncContext.mSyncOperation.authority
669 + " src=" + activeSyncContext.mSyncOperation.syncSource
670 + " extras=" + activeSyncContext.mSyncOperation.extras);
671 if (mActiveSync != null) {
672 Log.w(TAG, "setActiveSync called with existing active sync!");
673 }
674 AuthorityInfo authority = getAuthorityLocked(
675 activeSyncContext.mSyncOperation.account,
676 activeSyncContext.mSyncOperation.authority,
677 "setActiveSync");
678 if (authority == null) {
679 return;
680 }
681 mActiveSync = new ActiveSyncInfo(authority.ident,
682 authority.account, authority.authority,
683 activeSyncContext.mStartTime);
684 } else {
685 if (DEBUG) Log.v(TAG, "setActiveSync: null");
686 mActiveSync = null;
687 }
688 }
689
Fred Quintanaac9385e2009-06-22 18:00:59 -0700690 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700691 }
692
693 /**
694 * To allow others to send active change reports, to poke clients.
695 */
696 public void reportActiveChange() {
Fred Quintanaac9385e2009-06-22 18:00:59 -0700697 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700698 }
699
700 /**
701 * Note that sync has started for the given account and authority.
702 */
Dianne Hackborn7a135592009-05-06 00:28:37 -0700703 public long insertStartSyncEvent(Account accountName, String authorityName,
Dianne Hackborn231cc602009-04-27 17:10:36 -0700704 long now, int source) {
705 long id;
706 synchronized (mAuthorities) {
707 if (DEBUG) Log.v(TAG, "insertStartSyncEvent: account=" + accountName
708 + " auth=" + authorityName + " source=" + source);
709 AuthorityInfo authority = getAuthorityLocked(accountName, authorityName,
710 "insertStartSyncEvent");
711 if (authority == null) {
712 return -1;
713 }
714 SyncHistoryItem item = new SyncHistoryItem();
715 item.authorityId = authority.ident;
716 item.historyId = mNextHistoryId++;
717 if (mNextHistoryId < 0) mNextHistoryId = 0;
718 item.eventTime = now;
719 item.source = source;
720 item.event = EVENT_START;
721 mSyncHistory.add(0, item);
722 while (mSyncHistory.size() > MAX_HISTORY) {
723 mSyncHistory.remove(mSyncHistory.size()-1);
724 }
725 id = item.historyId;
726 if (DEBUG) Log.v(TAG, "returning historyId " + id);
727 }
728
Fred Quintanaac9385e2009-06-22 18:00:59 -0700729 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700730 return id;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800731 }
732
733 public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage,
734 long downstreamActivity, long upstreamActivity) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700735 synchronized (mAuthorities) {
736 if (DEBUG) Log.v(TAG, "stopSyncEvent: historyId=" + historyId);
737 SyncHistoryItem item = null;
738 int i = mSyncHistory.size();
739 while (i > 0) {
740 i--;
741 item = mSyncHistory.get(i);
742 if (item.historyId == historyId) {
743 break;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800744 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700745 item = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800746 }
Dianne Hackborn231cc602009-04-27 17:10:36 -0700747
748 if (item == null) {
749 Log.w(TAG, "stopSyncEvent: no history for id " + historyId);
750 return;
751 }
752
753 item.elapsedTime = elapsedTime;
754 item.event = EVENT_STOP;
755 item.mesg = resultMessage;
756 item.downstreamActivity = downstreamActivity;
757 item.upstreamActivity = upstreamActivity;
758
759 SyncStatusInfo status = getOrCreateSyncStatusLocked(item.authorityId);
760
761 status.numSyncs++;
762 status.totalElapsedTime += elapsedTime;
763 switch (item.source) {
764 case SOURCE_LOCAL:
765 status.numSourceLocal++;
766 break;
767 case SOURCE_POLL:
768 status.numSourcePoll++;
769 break;
770 case SOURCE_USER:
771 status.numSourceUser++;
772 break;
773 case SOURCE_SERVER:
774 status.numSourceServer++;
775 break;
776 }
777
778 boolean writeStatisticsNow = false;
Dianne Hackborn55280a92009-05-07 15:53:46 -0700779 int day = getCurrentDayLocked();
Dianne Hackborn231cc602009-04-27 17:10:36 -0700780 if (mDayStats[0] == null) {
781 mDayStats[0] = new DayStats(day);
782 } else if (day != mDayStats[0].day) {
783 System.arraycopy(mDayStats, 0, mDayStats, 1, mDayStats.length-1);
784 mDayStats[0] = new DayStats(day);
785 writeStatisticsNow = true;
786 } else if (mDayStats[0] == null) {
787 }
788 final DayStats ds = mDayStats[0];
789
790 final long lastSyncTime = (item.eventTime + elapsedTime);
791 boolean writeStatusNow = false;
792 if (MESG_SUCCESS.equals(resultMessage)) {
793 // - if successful, update the successful columns
794 if (status.lastSuccessTime == 0 || status.lastFailureTime != 0) {
795 writeStatusNow = true;
796 }
797 status.lastSuccessTime = lastSyncTime;
798 status.lastSuccessSource = item.source;
799 status.lastFailureTime = 0;
800 status.lastFailureSource = -1;
801 status.lastFailureMesg = null;
802 status.initialFailureTime = 0;
803 ds.successCount++;
804 ds.successTime += elapsedTime;
805 } else if (!MESG_CANCELED.equals(resultMessage)) {
806 if (status.lastFailureTime == 0) {
807 writeStatusNow = true;
808 }
809 status.lastFailureTime = lastSyncTime;
810 status.lastFailureSource = item.source;
811 status.lastFailureMesg = resultMessage;
812 if (status.initialFailureTime == 0) {
813 status.initialFailureTime = lastSyncTime;
814 }
815 ds.failureCount++;
816 ds.failureTime += elapsedTime;
817 }
818
819 if (writeStatusNow) {
820 writeStatusLocked();
821 } else if (!hasMessages(MSG_WRITE_STATUS)) {
822 sendMessageDelayed(obtainMessage(MSG_WRITE_STATUS),
823 WRITE_STATUS_DELAY);
824 }
825 if (writeStatisticsNow) {
826 writeStatisticsLocked();
827 } else if (!hasMessages(MSG_WRITE_STATISTICS)) {
828 sendMessageDelayed(obtainMessage(MSG_WRITE_STATISTICS),
829 WRITE_STATISTICS_DELAY);
830 }
831 }
832
Fred Quintanaac9385e2009-06-22 18:00:59 -0700833 reportChange(ContentResolver.SYNC_OBSERVER_TYPE_STATUS);
Dianne Hackborn231cc602009-04-27 17:10:36 -0700834 }
835
836 /**
837 * Return the currently active sync information, or null if there is no
838 * active sync. Note that the returned object is the real, live active
839 * sync object, so be careful what you do with it.
840 */
841 public ActiveSyncInfo getActiveSync() {
842 synchronized (mAuthorities) {
843 return mActiveSync;
844 }
845 }
846
847 /**
848 * Return an array of the current sync status for all authorities. Note
849 * that the objects inside the array are the real, live status objects,
850 * so be careful what you do with them.
851 */
852 public ArrayList<SyncStatusInfo> getSyncStatus() {
853 synchronized (mAuthorities) {
854 final int N = mSyncStatus.size();
855 ArrayList<SyncStatusInfo> ops = new ArrayList<SyncStatusInfo>(N);
856 for (int i=0; i<N; i++) {
857 ops.add(mSyncStatus.valueAt(i));
858 }
859 return ops;
860 }
861 }
862
863 /**
864 * Returns the status that matches the authority. If there are multiples accounts for
865 * the authority, the one with the latest "lastSuccessTime" status is returned.
866 * @param authority the authority whose row should be selected
867 * @return the SyncStatusInfo for the authority, or null if none exists
868 */
869 public SyncStatusInfo getStatusByAuthority(String authority) {
870 synchronized (mAuthorities) {
871 SyncStatusInfo best = null;
872 final int N = mSyncStatus.size();
873 for (int i=0; i<N; i++) {
874 SyncStatusInfo cur = mSyncStatus.get(i);
875 AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
876 if (ainfo != null && ainfo.authority.equals(authority)) {
877 if (best == null) {
878 best = cur;
879 } else if (best.lastSuccessTime > cur.lastSuccessTime) {
880 best = cur;
881 }
882 }
883 }
884 return best;
885 }
886 }
887
888 /**
889 * Return true if the pending status is true of any matching authorities.
890 */
Fred Quintanaac9385e2009-06-22 18:00:59 -0700891 public boolean isSyncPending(Account account, String authority) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700892 synchronized (mAuthorities) {
893 final int N = mSyncStatus.size();
894 for (int i=0; i<N; i++) {
895 SyncStatusInfo cur = mSyncStatus.get(i);
896 AuthorityInfo ainfo = mAuthorities.get(cur.authorityId);
897 if (ainfo == null) {
898 continue;
899 }
900 if (account != null && !ainfo.account.equals(account)) {
901 continue;
902 }
903 if (ainfo.authority.equals(authority) && cur.pending) {
904 return true;
905 }
906 }
907 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800908 }
909 }
910
911 /**
Dianne Hackborn231cc602009-04-27 17:10:36 -0700912 * Return an array of the current sync status for all authorities. Note
913 * that the objects inside the array are the real, live status objects,
914 * so be careful what you do with them.
915 */
916 public ArrayList<SyncHistoryItem> getSyncHistory() {
917 synchronized (mAuthorities) {
918 final int N = mSyncHistory.size();
919 ArrayList<SyncHistoryItem> items = new ArrayList<SyncHistoryItem>(N);
920 for (int i=0; i<N; i++) {
921 items.add(mSyncHistory.get(i));
922 }
923 return items;
924 }
925 }
926
927 /**
928 * Return an array of the current per-day statistics. Note
929 * that the objects inside the array are the real, live status objects,
930 * so be careful what you do with them.
931 */
932 public DayStats[] getDayStatistics() {
933 synchronized (mAuthorities) {
934 DayStats[] ds = new DayStats[mDayStats.length];
935 System.arraycopy(mDayStats, 0, ds, 0, ds.length);
936 return ds;
937 }
938 }
939
940 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800941 * If sync is failing for any of the provider/accounts then determine the time at which it
942 * started failing and return the earliest time over all the provider/accounts. If none are
943 * failing then return 0.
944 */
945 public long getInitialSyncFailureTime() {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700946 synchronized (mAuthorities) {
Fred Quintanaac9385e2009-06-22 18:00:59 -0700947 if (!mMasterSyncAutomatically) {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700948 return 0;
949 }
950
951 long oldest = 0;
952 int i = mSyncStatus.size();
953 while (i > 0) {
954 i--;
955 SyncStatusInfo stats = mSyncStatus.valueAt(i);
956 AuthorityInfo authority = mAuthorities.get(stats.authorityId);
957 if (authority != null && authority.enabled) {
958 if (oldest == 0 || stats.initialFailureTime < oldest) {
959 oldest = stats.initialFailureTime;
960 }
961 }
962 }
963
964 return oldest;
965 }
966 }
967
Dianne Hackborn55280a92009-05-07 15:53:46 -0700968 private int getCurrentDayLocked() {
Dianne Hackborn231cc602009-04-27 17:10:36 -0700969 mCal.setTimeInMillis(System.currentTimeMillis());
970 final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR);
971 if (mYear != mCal.get(Calendar.YEAR)) {
972 mYear = mCal.get(Calendar.YEAR);
973 mCal.clear();
974 mCal.set(Calendar.YEAR, mYear);
975 mYearInDays = (int)(mCal.getTimeInMillis()/86400000);
976 }
977 return dayOfYear + mYearInDays;
978 }
979
980 /**
981 * Retrieve an authority, returning null if one does not exist.
982 *
983 * @param accountName The name of the account for the authority.
984 * @param authorityName The name of the authority itself.
985 * @param tag If non-null, this will be used in a log message if the
986 * requested authority does not exist.
987 */
Dianne Hackborn7a135592009-05-06 00:28:37 -0700988 private AuthorityInfo getAuthorityLocked(Account accountName, String authorityName,
Dianne Hackborn231cc602009-04-27 17:10:36 -0700989 String tag) {
990 AccountInfo account = mAccounts.get(accountName);
991 if (account == null) {
992 if (tag != null) {
993 Log.w(TAG, tag + ": unknown account " + accountName);
994 }
995 return null;
996 }
997 AuthorityInfo authority = account.authorities.get(authorityName);
998 if (authority == null) {
999 if (tag != null) {
1000 Log.w(TAG, tag + ": unknown authority " + authorityName);
1001 }
1002 return null;
1003 }
1004
1005 return authority;
1006 }
1007
Dianne Hackborn7a135592009-05-06 00:28:37 -07001008 private AuthorityInfo getOrCreateAuthorityLocked(Account accountName,
Dianne Hackborn231cc602009-04-27 17:10:36 -07001009 String authorityName, int ident, boolean doWrite) {
1010 AccountInfo account = mAccounts.get(accountName);
1011 if (account == null) {
1012 account = new AccountInfo(accountName);
1013 mAccounts.put(accountName, account);
1014 }
1015 AuthorityInfo authority = account.authorities.get(authorityName);
1016 if (authority == null) {
1017 if (ident < 0) {
1018 // Look for a new identifier for this authority.
1019 final int N = mAuthorities.size();
1020 ident = 0;
1021 for (int i=0; i<N; i++) {
1022 if (mAuthorities.valueAt(i).ident > ident) {
1023 break;
1024 }
1025 ident++;
1026 }
1027 }
1028 authority = new AuthorityInfo(accountName, authorityName, ident);
1029 account.authorities.put(authorityName, authority);
1030 mAuthorities.put(ident, authority);
1031 if (doWrite) {
1032 writeAccountInfoLocked();
1033 }
1034 }
1035
1036 return authority;
1037 }
1038
1039 private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) {
1040 SyncStatusInfo status = mSyncStatus.get(authorityId);
1041 if (status == null) {
1042 status = new SyncStatusInfo(authorityId);
1043 mSyncStatus.put(authorityId, status);
1044 }
1045 return status;
1046 }
1047
Dianne Hackborn55280a92009-05-07 15:53:46 -07001048 public void writeAllState() {
1049 synchronized (mAuthorities) {
1050 // Account info is always written so no need to do it here.
1051
1052 if (mNumPendingFinished > 0) {
1053 // Only write these if they are out of date.
1054 writePendingOperationsLocked();
1055 }
1056
1057 // Just always write these... they are likely out of date.
1058 writeStatusLocked();
1059 writeStatisticsLocked();
1060 }
1061 }
1062
Dianne Hackborn231cc602009-04-27 17:10:36 -07001063 /**
1064 * Read all account information back in to the initial engine state.
1065 */
1066 private void readAccountInfoLocked() {
1067 FileInputStream fis = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001068 try {
Dianne Hackborn231cc602009-04-27 17:10:36 -07001069 fis = mAccountInfoFile.openRead();
1070 if (DEBUG_FILE) Log.v(TAG, "Reading " + mAccountInfoFile.getBaseFile());
1071 XmlPullParser parser = Xml.newPullParser();
1072 parser.setInput(fis, null);
1073 int eventType = parser.getEventType();
1074 while (eventType != XmlPullParser.START_TAG) {
1075 eventType = parser.next();
1076 }
1077 String tagName = parser.getName();
1078 if ("accounts".equals(tagName)) {
1079 String listen = parser.getAttributeValue(
1080 null, "listen-for-tickles");
Fred Quintanaac9385e2009-06-22 18:00:59 -07001081 mMasterSyncAutomatically = listen == null
Dianne Hackborn231cc602009-04-27 17:10:36 -07001082 || Boolean.parseBoolean(listen);
1083 eventType = parser.next();
1084 do {
1085 if (eventType == XmlPullParser.START_TAG
1086 && parser.getDepth() == 2) {
1087 tagName = parser.getName();
1088 if ("authority".equals(tagName)) {
1089 int id = -1;
1090 try {
1091 id = Integer.parseInt(parser.getAttributeValue(
1092 null, "id"));
1093 } catch (NumberFormatException e) {
1094 } catch (NullPointerException e) {
1095 }
1096 if (id >= 0) {
1097 String accountName = parser.getAttributeValue(
1098 null, "account");
Dianne Hackborn7a135592009-05-06 00:28:37 -07001099 String accountType = parser.getAttributeValue(
1100 null, "type");
1101 if (accountType == null) {
1102 accountType = "com.google.GAIA";
1103 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001104 String authorityName = parser.getAttributeValue(
1105 null, "authority");
1106 String enabled = parser.getAttributeValue(
1107 null, "enabled");
Fred Quintana5e787c42009-08-16 23:13:53 -07001108 String syncable = parser.getAttributeValue(null, "syncable");
1109 AuthorityInfo authority = mAuthorities.get(id);
Dianne Hackborn231cc602009-04-27 17:10:36 -07001110 if (DEBUG_FILE) Log.v(TAG, "Adding authority: account="
1111 + accountName + " auth=" + authorityName
Fred Quintana5e787c42009-08-16 23:13:53 -07001112 + " enabled=" + enabled
1113 + " syncable=" + syncable);
Dianne Hackborn231cc602009-04-27 17:10:36 -07001114 if (authority == null) {
1115 if (DEBUG_FILE) Log.v(TAG, "Creating entry");
1116 authority = getOrCreateAuthorityLocked(
Dianne Hackborn7a135592009-05-06 00:28:37 -07001117 new Account(accountName, accountType),
1118 authorityName, id, false);
Dianne Hackborn231cc602009-04-27 17:10:36 -07001119 }
1120 if (authority != null) {
1121 authority.enabled = enabled == null
1122 || Boolean.parseBoolean(enabled);
Fred Quintana5e787c42009-08-16 23:13:53 -07001123 if ("unknown".equals(syncable)) {
1124 authority.syncable = -1;
1125 } else {
1126 authority.syncable =
1127 (syncable == null || Boolean.parseBoolean(enabled))
1128 ? 1
1129 : 0;
1130 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001131 } else {
1132 Log.w(TAG, "Failure adding authority: account="
1133 + accountName + " auth=" + authorityName
Fred Quintana5e787c42009-08-16 23:13:53 -07001134 + " enabled=" + enabled
1135 + " syncable=" + syncable);
Dianne Hackborn231cc602009-04-27 17:10:36 -07001136 }
1137 }
1138 }
1139 }
1140 eventType = parser.next();
1141 } while (eventType != XmlPullParser.END_DOCUMENT);
1142 }
1143 } catch (XmlPullParserException e) {
1144 Log.w(TAG, "Error reading accounts", e);
1145 } catch (java.io.IOException e) {
1146 if (fis == null) Log.i(TAG, "No initial accounts");
1147 else Log.w(TAG, "Error reading accounts", e);
1148 } finally {
1149 if (fis != null) {
1150 try {
1151 fis.close();
1152 } catch (java.io.IOException e1) {
1153 }
1154 }
1155 }
1156 }
1157
1158 /**
1159 * Write all account information to the account file.
1160 */
1161 private void writeAccountInfoLocked() {
1162 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mAccountInfoFile.getBaseFile());
1163 FileOutputStream fos = null;
1164
1165 try {
1166 fos = mAccountInfoFile.startWrite();
1167 XmlSerializer out = new FastXmlSerializer();
1168 out.setOutput(fos, "utf-8");
1169 out.startDocument(null, true);
1170 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
1171
1172 out.startTag(null, "accounts");
Fred Quintanaac9385e2009-06-22 18:00:59 -07001173 if (!mMasterSyncAutomatically) {
Dianne Hackborn231cc602009-04-27 17:10:36 -07001174 out.attribute(null, "listen-for-tickles", "false");
1175 }
1176
1177 final int N = mAuthorities.size();
1178 for (int i=0; i<N; i++) {
1179 AuthorityInfo authority = mAuthorities.get(i);
1180 out.startTag(null, "authority");
1181 out.attribute(null, "id", Integer.toString(authority.ident));
Fred Quintanaffd0cb042009-08-15 21:45:26 -07001182 out.attribute(null, "account", authority.account.name);
1183 out.attribute(null, "type", authority.account.type);
Dianne Hackborn231cc602009-04-27 17:10:36 -07001184 out.attribute(null, "authority", authority.authority);
1185 if (!authority.enabled) {
1186 out.attribute(null, "enabled", "false");
1187 }
Fred Quintana5e787c42009-08-16 23:13:53 -07001188 if (authority.syncable < 0) {
1189 out.attribute(null, "syncable", "unknown");
1190 } else if (authority.syncable == 0) {
1191 out.attribute(null, "syncable", "false");
1192 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001193 out.endTag(null, "authority");
1194 }
1195
1196 out.endTag(null, "accounts");
1197
1198 out.endDocument();
1199
1200 mAccountInfoFile.finishWrite(fos);
1201 } catch (java.io.IOException e1) {
1202 Log.w(TAG, "Error writing accounts", e1);
1203 if (fos != null) {
1204 mAccountInfoFile.failWrite(fos);
1205 }
1206 }
1207 }
1208
1209 static int getIntColumn(Cursor c, String name) {
1210 return c.getInt(c.getColumnIndex(name));
1211 }
1212
1213 static long getLongColumn(Cursor c, String name) {
1214 return c.getLong(c.getColumnIndex(name));
1215 }
1216
1217 /**
1218 * Load sync engine state from the old syncmanager database, and then
1219 * erase it. Note that we don't deal with pending operations, active
1220 * sync, or history.
1221 */
1222 private void readLegacyAccountInfoLocked() {
1223 // Look for old database to initialize from.
1224 File file = mContext.getDatabasePath("syncmanager.db");
1225 if (!file.exists()) {
1226 return;
1227 }
1228 String path = file.getPath();
1229 SQLiteDatabase db = null;
1230 try {
1231 db = SQLiteDatabase.openDatabase(path, null,
1232 SQLiteDatabase.OPEN_READONLY);
1233 } catch (SQLiteException e) {
1234 }
1235
1236 if (db != null) {
Dianne Hackborn2d5ed1f2009-05-06 15:22:38 -07001237 final boolean hasType = db.getVersion() >= 11;
1238
Dianne Hackborn231cc602009-04-27 17:10:36 -07001239 // Copy in all of the status information, as well as accounts.
1240 if (DEBUG_FILE) Log.v(TAG, "Reading legacy sync accounts db");
1241 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
1242 qb.setTables("stats, status");
1243 HashMap<String,String> map = new HashMap<String,String>();
1244 map.put("_id", "status._id as _id");
1245 map.put("account", "stats.account as account");
Dianne Hackborn2d5ed1f2009-05-06 15:22:38 -07001246 if (hasType) {
1247 map.put("account_type", "stats.account_type as account_type");
1248 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001249 map.put("authority", "stats.authority as authority");
1250 map.put("totalElapsedTime", "totalElapsedTime");
1251 map.put("numSyncs", "numSyncs");
1252 map.put("numSourceLocal", "numSourceLocal");
1253 map.put("numSourcePoll", "numSourcePoll");
1254 map.put("numSourceServer", "numSourceServer");
1255 map.put("numSourceUser", "numSourceUser");
1256 map.put("lastSuccessSource", "lastSuccessSource");
1257 map.put("lastSuccessTime", "lastSuccessTime");
1258 map.put("lastFailureSource", "lastFailureSource");
1259 map.put("lastFailureTime", "lastFailureTime");
1260 map.put("lastFailureMesg", "lastFailureMesg");
1261 map.put("pending", "pending");
1262 qb.setProjectionMap(map);
1263 qb.appendWhere("stats._id = status.stats_id");
1264 Cursor c = qb.query(db, null, null, null, null, null, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001265 while (c.moveToNext()) {
Dianne Hackborn231cc602009-04-27 17:10:36 -07001266 String accountName = c.getString(c.getColumnIndex("account"));
Dianne Hackborn2d5ed1f2009-05-06 15:22:38 -07001267 String accountType = hasType
1268 ? c.getString(c.getColumnIndex("account_type")) : null;
Dianne Hackborn7a135592009-05-06 00:28:37 -07001269 if (accountType == null) {
1270 accountType = "com.google.GAIA";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001271 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001272 String authorityName = c.getString(c.getColumnIndex("authority"));
1273 AuthorityInfo authority = this.getOrCreateAuthorityLocked(
Dianne Hackborn7a135592009-05-06 00:28:37 -07001274 new Account(accountName, accountType),
1275 authorityName, -1, false);
Dianne Hackborn231cc602009-04-27 17:10:36 -07001276 if (authority != null) {
1277 int i = mSyncStatus.size();
1278 boolean found = false;
1279 SyncStatusInfo st = null;
1280 while (i > 0) {
1281 i--;
1282 st = mSyncStatus.get(i);
1283 if (st.authorityId == authority.ident) {
1284 found = true;
1285 break;
1286 }
1287 }
1288 if (!found) {
1289 st = new SyncStatusInfo(authority.ident);
1290 mSyncStatus.put(authority.ident, st);
1291 }
1292 st.totalElapsedTime = getLongColumn(c, "totalElapsedTime");
1293 st.numSyncs = getIntColumn(c, "numSyncs");
1294 st.numSourceLocal = getIntColumn(c, "numSourceLocal");
1295 st.numSourcePoll = getIntColumn(c, "numSourcePoll");
1296 st.numSourceServer = getIntColumn(c, "numSourceServer");
1297 st.numSourceUser = getIntColumn(c, "numSourceUser");
1298 st.lastSuccessSource = getIntColumn(c, "lastSuccessSource");
1299 st.lastSuccessTime = getLongColumn(c, "lastSuccessTime");
1300 st.lastFailureSource = getIntColumn(c, "lastFailureSource");
1301 st.lastFailureTime = getLongColumn(c, "lastFailureTime");
1302 st.lastFailureMesg = c.getString(c.getColumnIndex("lastFailureMesg"));
1303 st.pending = getIntColumn(c, "pending") != 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001304 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001305 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001306
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001307 c.close();
Dianne Hackborn231cc602009-04-27 17:10:36 -07001308
1309 // Retrieve the settings.
1310 qb = new SQLiteQueryBuilder();
1311 qb.setTables("settings");
1312 c = qb.query(db, null, null, null, null, null, null);
1313 while (c.moveToNext()) {
1314 String name = c.getString(c.getColumnIndex("name"));
1315 String value = c.getString(c.getColumnIndex("value"));
1316 if (name == null) continue;
1317 if (name.equals("listen_for_tickles")) {
Fred Quintanaac9385e2009-06-22 18:00:59 -07001318 setMasterSyncAutomatically(value == null || Boolean.parseBoolean(value));
Dianne Hackborn231cc602009-04-27 17:10:36 -07001319 } else if (name.startsWith("sync_provider_")) {
1320 String provider = name.substring("sync_provider_".length(),
1321 name.length());
Fred Quintanaac9385e2009-06-22 18:00:59 -07001322 int i = mAuthorities.size();
1323 while (i > 0) {
1324 i--;
1325 AuthorityInfo authority = mAuthorities.get(i);
1326 if (authority.authority.equals(provider)) {
1327 authority.enabled = value == null || Boolean.parseBoolean(value);
Fred Quintana5e787c42009-08-16 23:13:53 -07001328 authority.syncable = 1;
Fred Quintanaac9385e2009-06-22 18:00:59 -07001329 }
1330 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001331 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001332 }
Dianne Hackborn231cc602009-04-27 17:10:36 -07001333
1334 c.close();
1335
1336 db.close();
1337
1338 writeAccountInfoLocked();
1339 writeStatusLocked();
1340 (new File(path)).delete();
1341 }
1342 }
1343
1344 public static final int STATUS_FILE_END = 0;
1345 public static final int STATUS_FILE_ITEM = 100;
1346
1347 /**
1348 * Read all sync status back in to the initial engine state.
1349 */
1350 private void readStatusLocked() {
1351 if (DEBUG_FILE) Log.v(TAG, "Reading " + mStatusFile.getBaseFile());
1352 try {
1353 byte[] data = mStatusFile.readFully();
1354 Parcel in = Parcel.obtain();
1355 in.unmarshall(data, 0, data.length);
1356 in.setDataPosition(0);
1357 int token;
1358 while ((token=in.readInt()) != STATUS_FILE_END) {
1359 if (token == STATUS_FILE_ITEM) {
1360 SyncStatusInfo status = new SyncStatusInfo(in);
1361 if (mAuthorities.indexOfKey(status.authorityId) >= 0) {
1362 status.pending = false;
1363 if (DEBUG_FILE) Log.v(TAG, "Adding status for id "
1364 + status.authorityId);
1365 mSyncStatus.put(status.authorityId, status);
1366 }
1367 } else {
1368 // Ooops.
1369 Log.w(TAG, "Unknown status token: " + token);
1370 break;
1371 }
1372 }
1373 } catch (java.io.IOException e) {
1374 Log.i(TAG, "No initial status");
1375 }
1376 }
1377
1378 /**
1379 * Write all sync status to the sync status file.
1380 */
1381 private void writeStatusLocked() {
1382 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatusFile.getBaseFile());
1383
1384 // The file is being written, so we don't need to have a scheduled
1385 // write until the next change.
1386 removeMessages(MSG_WRITE_STATUS);
1387
1388 FileOutputStream fos = null;
1389 try {
1390 fos = mStatusFile.startWrite();
1391 Parcel out = Parcel.obtain();
1392 final int N = mSyncStatus.size();
1393 for (int i=0; i<N; i++) {
1394 SyncStatusInfo status = mSyncStatus.valueAt(i);
1395 out.writeInt(STATUS_FILE_ITEM);
1396 status.writeToParcel(out, 0);
1397 }
1398 out.writeInt(STATUS_FILE_END);
1399 fos.write(out.marshall());
1400 out.recycle();
1401
1402 mStatusFile.finishWrite(fos);
1403 } catch (java.io.IOException e1) {
1404 Log.w(TAG, "Error writing status", e1);
1405 if (fos != null) {
1406 mStatusFile.failWrite(fos);
1407 }
1408 }
1409 }
1410
1411 public static final int PENDING_OPERATION_VERSION = 1;
1412
1413 /**
1414 * Read all pending operations back in to the initial engine state.
1415 */
1416 private void readPendingOperationsLocked() {
1417 if (DEBUG_FILE) Log.v(TAG, "Reading " + mPendingFile.getBaseFile());
1418 try {
1419 byte[] data = mPendingFile.readFully();
1420 Parcel in = Parcel.obtain();
1421 in.unmarshall(data, 0, data.length);
1422 in.setDataPosition(0);
1423 final int SIZE = in.dataSize();
1424 while (in.dataPosition() < SIZE) {
1425 int version = in.readInt();
1426 if (version != PENDING_OPERATION_VERSION) {
1427 Log.w(TAG, "Unknown pending operation version "
1428 + version + "; dropping all ops");
1429 break;
1430 }
1431 int authorityId = in.readInt();
1432 int syncSource = in.readInt();
1433 byte[] flatExtras = in.createByteArray();
1434 AuthorityInfo authority = mAuthorities.get(authorityId);
1435 if (authority != null) {
1436 Bundle extras = null;
1437 if (flatExtras != null) {
1438 extras = unflattenBundle(flatExtras);
1439 }
1440 PendingOperation op = new PendingOperation(
1441 authority.account, syncSource,
1442 authority.authority, extras);
1443 op.authorityId = authorityId;
1444 op.flatExtras = flatExtras;
1445 if (DEBUG_FILE) Log.v(TAG, "Adding pending op: account=" + op.account
1446 + " auth=" + op.authority
1447 + " src=" + op.syncSource
1448 + " extras=" + op.extras);
1449 mPendingOperations.add(op);
1450 }
1451 }
1452 } catch (java.io.IOException e) {
1453 Log.i(TAG, "No initial pending operations");
1454 }
1455 }
1456
1457 private void writePendingOperationLocked(PendingOperation op, Parcel out) {
1458 out.writeInt(PENDING_OPERATION_VERSION);
1459 out.writeInt(op.authorityId);
1460 out.writeInt(op.syncSource);
1461 if (op.flatExtras == null && op.extras != null) {
1462 op.flatExtras = flattenBundle(op.extras);
1463 }
1464 out.writeByteArray(op.flatExtras);
1465 }
1466
1467 /**
1468 * Write all currently pending ops to the pending ops file.
1469 */
1470 private void writePendingOperationsLocked() {
1471 final int N = mPendingOperations.size();
1472 FileOutputStream fos = null;
1473 try {
1474 if (N == 0) {
1475 if (DEBUG_FILE) Log.v(TAG, "Truncating " + mPendingFile.getBaseFile());
1476 mPendingFile.truncate();
1477 return;
1478 }
1479
1480 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mPendingFile.getBaseFile());
1481 fos = mPendingFile.startWrite();
1482
1483 Parcel out = Parcel.obtain();
1484 for (int i=0; i<N; i++) {
1485 PendingOperation op = mPendingOperations.get(i);
1486 writePendingOperationLocked(op, out);
1487 }
1488 fos.write(out.marshall());
1489 out.recycle();
1490
1491 mPendingFile.finishWrite(fos);
1492 } catch (java.io.IOException e1) {
1493 Log.w(TAG, "Error writing pending operations", e1);
1494 if (fos != null) {
1495 mPendingFile.failWrite(fos);
1496 }
1497 }
1498 }
1499
1500 /**
1501 * Append the given operation to the pending ops file; if unable to,
1502 * write all pending ops.
1503 */
1504 private void appendPendingOperationLocked(PendingOperation op) {
1505 if (DEBUG_FILE) Log.v(TAG, "Appending to " + mPendingFile.getBaseFile());
1506 FileOutputStream fos = null;
1507 try {
1508 fos = mPendingFile.openAppend();
1509 } catch (java.io.IOException e) {
1510 if (DEBUG_FILE) Log.v(TAG, "Failed append; writing full file");
1511 writePendingOperationsLocked();
1512 return;
1513 }
1514
1515 try {
1516 Parcel out = Parcel.obtain();
1517 writePendingOperationLocked(op, out);
1518 fos.write(out.marshall());
1519 out.recycle();
1520 } catch (java.io.IOException e1) {
1521 Log.w(TAG, "Error writing pending operations", e1);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001522 } finally {
Dianne Hackborn231cc602009-04-27 17:10:36 -07001523 try {
1524 fos.close();
1525 } catch (java.io.IOException e2) {
1526 }
1527 }
1528 }
1529
1530 static private byte[] flattenBundle(Bundle bundle) {
1531 byte[] flatData = null;
1532 Parcel parcel = Parcel.obtain();
1533 try {
1534 bundle.writeToParcel(parcel, 0);
1535 flatData = parcel.marshall();
1536 } finally {
1537 parcel.recycle();
1538 }
1539 return flatData;
1540 }
1541
1542 static private Bundle unflattenBundle(byte[] flatData) {
1543 Bundle bundle;
1544 Parcel parcel = Parcel.obtain();
1545 try {
1546 parcel.unmarshall(flatData, 0, flatData.length);
1547 parcel.setDataPosition(0);
1548 bundle = parcel.readBundle();
1549 } catch (RuntimeException e) {
1550 // A RuntimeException is thrown if we were unable to parse the parcel.
1551 // Create an empty parcel in this case.
1552 bundle = new Bundle();
1553 } finally {
1554 parcel.recycle();
1555 }
1556 return bundle;
1557 }
1558
1559 public static final int STATISTICS_FILE_END = 0;
1560 public static final int STATISTICS_FILE_ITEM_OLD = 100;
1561 public static final int STATISTICS_FILE_ITEM = 101;
1562
1563 /**
1564 * Read all sync statistics back in to the initial engine state.
1565 */
1566 private void readStatisticsLocked() {
1567 try {
1568 byte[] data = mStatisticsFile.readFully();
1569 Parcel in = Parcel.obtain();
1570 in.unmarshall(data, 0, data.length);
1571 in.setDataPosition(0);
1572 int token;
1573 int index = 0;
1574 while ((token=in.readInt()) != STATISTICS_FILE_END) {
1575 if (token == STATISTICS_FILE_ITEM
1576 || token == STATISTICS_FILE_ITEM_OLD) {
1577 int day = in.readInt();
1578 if (token == STATISTICS_FILE_ITEM_OLD) {
1579 day = day - 2009 + 14245; // Magic!
1580 }
1581 DayStats ds = new DayStats(day);
1582 ds.successCount = in.readInt();
1583 ds.successTime = in.readLong();
1584 ds.failureCount = in.readInt();
1585 ds.failureTime = in.readLong();
1586 if (index < mDayStats.length) {
1587 mDayStats[index] = ds;
1588 index++;
1589 }
1590 } else {
1591 // Ooops.
1592 Log.w(TAG, "Unknown stats token: " + token);
1593 break;
1594 }
1595 }
1596 } catch (java.io.IOException e) {
1597 Log.i(TAG, "No initial statistics");
1598 }
1599 }
1600
1601 /**
1602 * Write all sync statistics to the sync status file.
1603 */
1604 private void writeStatisticsLocked() {
1605 if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatisticsFile.getBaseFile());
1606
1607 // The file is being written, so we don't need to have a scheduled
1608 // write until the next change.
1609 removeMessages(MSG_WRITE_STATISTICS);
1610
1611 FileOutputStream fos = null;
1612 try {
1613 fos = mStatisticsFile.startWrite();
1614 Parcel out = Parcel.obtain();
1615 final int N = mDayStats.length;
1616 for (int i=0; i<N; i++) {
1617 DayStats ds = mDayStats[i];
1618 if (ds == null) {
1619 break;
1620 }
1621 out.writeInt(STATISTICS_FILE_ITEM);
1622 out.writeInt(ds.day);
1623 out.writeInt(ds.successCount);
1624 out.writeLong(ds.successTime);
1625 out.writeInt(ds.failureCount);
1626 out.writeLong(ds.failureTime);
1627 }
1628 out.writeInt(STATISTICS_FILE_END);
1629 fos.write(out.marshall());
1630 out.recycle();
1631
1632 mStatisticsFile.finishWrite(fos);
1633 } catch (java.io.IOException e1) {
1634 Log.w(TAG, "Error writing stats", e1);
1635 if (fos != null) {
1636 mStatisticsFile.failWrite(fos);
1637 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001638 }
1639 }
1640}