summaryrefslogtreecommitdiffstats
path: root/core/java/android/content/SyncStorageEngine.java
diff options
context:
space:
mode:
authorFred Quintana <fredq@google.com>2010-01-27 12:17:49 -0800
committerFred Quintana <fredq@google.com>2010-02-05 15:16:20 -0800
commitc5d1c6db61f208b206b260f897bb5bbc64be4d97 (patch)
tree91a5ffe88326b446f57b7249ddf1406b8654d2f6 /core/java/android/content/SyncStorageEngine.java
parent0a45a09814dea0398647f26497ecff54a77c5f8c (diff)
downloadframeworks_base-c5d1c6db61f208b206b260f897bb5bbc64be4d97.zip
frameworks_base-c5d1c6db61f208b206b260f897bb5bbc64be4d97.tar.gz
frameworks_base-c5d1c6db61f208b206b260f897bb5bbc64be4d97.tar.bz2
add sync polling
- added the ability to specify that a sync (of account/authority/extras) should occur at a given frequency - the existing daily poll code was replaced with seeding each account/authority with a 24 hour periodic sync - enhanced the "adb shell dumpsys content" output to show the periodic syncs and when they will next run
Diffstat (limited to 'core/java/android/content/SyncStorageEngine.java')
-rw-r--r--core/java/android/content/SyncStorageEngine.java393
1 files changed, 327 insertions, 66 deletions
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index db70096..07a1f46 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -36,7 +36,6 @@ import android.os.Message;
import android.os.Parcel;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.util.Log;
import android.util.SparseArray;
import android.util.Xml;
@@ -50,6 +49,7 @@ import java.util.Calendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.TimeZone;
+import java.util.List;
/**
* Singleton that tracks the sync data and overall sync
@@ -62,6 +62,8 @@ public class SyncStorageEngine extends Handler {
private static final boolean DEBUG = false;
private static final boolean DEBUG_FILE = false;
+ private static final long DEFAULT_POLL_FREQUENCY_SECONDS = 60 * 60 * 24; // One day
+
// @VisibleForTesting
static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4;
@@ -89,6 +91,9 @@ public class SyncStorageEngine extends Handler {
/** Enum value for a user-initiated sync. */
public static final int SOURCE_USER = 3;
+ /** Enum value for a periodic sync. */
+ public static final int SOURCE_PERIODIC = 4;
+
public static final long NOT_IN_BACKOFF_MODE = -1;
private static final Intent SYNC_CONNECTION_SETTING_CHANGED_INTENT =
@@ -99,7 +104,8 @@ public class SyncStorageEngine extends Handler {
public static final String[] SOURCES = { "SERVER",
"LOCAL",
"POLL",
- "USER" };
+ "USER",
+ "PERIODIC" };
// The MESG column will contain one of these or one of the Error types.
public static final String MESG_SUCCESS = "success";
@@ -164,6 +170,7 @@ public class SyncStorageEngine extends Handler {
long backoffTime;
long backoffDelay;
long delayUntil;
+ final ArrayList<Pair<Bundle, Long>> periodicSyncs;
AuthorityInfo(Account account, String authority, int ident) {
this.account = account;
@@ -173,6 +180,8 @@ public class SyncStorageEngine extends Handler {
syncable = -1; // default to "unknown"
backoffTime = -1; // if < 0 then we aren't in backoff mode
backoffDelay = -1; // if < 0 then we aren't in backoff mode
+ periodicSyncs = new ArrayList<Pair<Bundle, Long>>();
+ periodicSyncs.add(Pair.create(new Bundle(), DEFAULT_POLL_FREQUENCY_SECONDS));
}
}
@@ -228,6 +237,7 @@ public class SyncStorageEngine extends Handler {
private int mYearInDays;
private final Context mContext;
+
private static volatile SyncStorageEngine sSyncStorageEngine = null;
/**
@@ -262,17 +272,15 @@ public class SyncStorageEngine extends Handler {
private int mNextHistoryId = 0;
private boolean mMasterSyncAutomatically = true;
- private SyncStorageEngine(Context context) {
+ private SyncStorageEngine(Context context, File dataDir) {
mContext = context;
sSyncStorageEngine = this;
mCal = Calendar.getInstance(TimeZone.getTimeZone("GMT+0"));
- // This call will return the correct directory whether Encrypted File Systems is
- // enabled or not.
- File dataDir = Environment.getSecureDataDirectory();
File systemDir = new File(dataDir, "system");
File syncDir = new File(systemDir, "sync");
+ syncDir.mkdirs();
mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml"));
mStatusFile = new AtomicFile(new File(syncDir, "status.bin"));
mPendingFile = new AtomicFile(new File(syncDir, "pending.bin"));
@@ -286,14 +294,17 @@ public class SyncStorageEngine extends Handler {
}
public static SyncStorageEngine newTestInstance(Context context) {
- return new SyncStorageEngine(context);
+ return new SyncStorageEngine(context, context.getFilesDir());
}
public static void init(Context context) {
if (sSyncStorageEngine != null) {
return;
}
- sSyncStorageEngine = new SyncStorageEngine(context);
+ // This call will return the correct directory whether Encrypted File Systems is
+ // enabled or not.
+ File dataDir = Environment.getSecureDataDirectory();
+ sSyncStorageEngine = new SyncStorageEngine(context, dataDir);
}
public static SyncStorageEngine getSingleton() {
@@ -475,7 +486,7 @@ public class SyncStorageEngine extends Handler {
}
} else {
AuthorityInfo authority =
- getOrCreateAuthorityLocked(account, providerName, -1, false);
+ getOrCreateAuthorityLocked(account, providerName, -1 /* ident */, true);
if (authority.backoffTime == nextSyncTime && authority.backoffDelay == nextDelay) {
return;
}
@@ -483,9 +494,6 @@ public class SyncStorageEngine extends Handler {
authority.backoffDelay = nextDelay;
changed = true;
}
- if (changed) {
- writeAccountInfoLocked();
- }
}
if (changed) {
@@ -499,12 +507,12 @@ public class SyncStorageEngine extends Handler {
+ " -> delayUntil " + delayUntil);
}
synchronized (mAuthorities) {
- AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false);
+ AuthorityInfo authority = getOrCreateAuthorityLocked(
+ account, providerName, -1 /* ident */, true);
if (authority.delayUntil == delayUntil) {
return;
}
authority.delayUntil = delayUntil;
- writeAccountInfoLocked();
}
reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
@@ -520,6 +528,90 @@ public class SyncStorageEngine extends Handler {
}
}
+ private void updateOrRemovePeriodicSync(Account account, String providerName, Bundle extras,
+ long period, boolean add) {
+ if (period <= 0) {
+ period = 0;
+ }
+ if (extras == null) {
+ extras = new Bundle();
+ }
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Log.v(TAG, "addOrRemovePeriodicSync: " + account + ", provider " + providerName
+ + " -> period " + period + ", extras " + extras);
+ }
+ synchronized (mAuthorities) {
+ AuthorityInfo authority = getOrCreateAuthorityLocked(account, providerName, -1, false);
+ if (add) {
+ boolean alreadyPresent = false;
+ for (int i = 0, N = authority.periodicSyncs.size(); i < N; i++) {
+ Pair<Bundle, Long> syncInfo = authority.periodicSyncs.get(i);
+ final Bundle existingExtras = syncInfo.first;
+ if (equals(existingExtras, extras)) {
+ if (syncInfo.second == period) {
+ return;
+ }
+ authority.periodicSyncs.set(i, Pair.create(extras, period));
+ alreadyPresent = true;
+ break;
+ }
+ }
+ if (!alreadyPresent) {
+ authority.periodicSyncs.add(Pair.create(extras, period));
+ SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident);
+ status.setPeriodicSyncTime(authority.periodicSyncs.size() - 1, 0);
+ }
+ } else {
+ SyncStatusInfo status = mSyncStatus.get(authority.ident);
+ boolean changed = false;
+ Iterator<Pair<Bundle, Long>> iterator = authority.periodicSyncs.iterator();
+ int i = 0;
+ while (iterator.hasNext()) {
+ Pair<Bundle, Long> syncInfo = iterator.next();
+ if (equals(syncInfo.first, extras)) {
+ iterator.remove();
+ changed = true;
+ if (status != null) {
+ status.removePeriodicSyncTime(i);
+ }
+ } else {
+ i++;
+ }
+ }
+ if (!changed) {
+ return;
+ }
+ }
+ writeAccountInfoLocked();
+ writeStatusLocked();
+ }
+
+ reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+ }
+
+ public void addPeriodicSync(Account account, String providerName, Bundle extras,
+ long pollFrequency) {
+ updateOrRemovePeriodicSync(account, providerName, extras, pollFrequency, true /* add */);
+ }
+
+ public void removePeriodicSync(Account account, String providerName, Bundle extras) {
+ updateOrRemovePeriodicSync(account, providerName, extras, 0 /* period, ignored */,
+ false /* remove */);
+ }
+
+ public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) {
+ ArrayList<PeriodicSync> syncs = new ArrayList<PeriodicSync>();
+ synchronized (mAuthorities) {
+ AuthorityInfo authority = getAuthorityLocked(account, providerName, "getPeriodicSyncs");
+ if (authority != null) {
+ for (Pair<Bundle, Long> item : authority.periodicSyncs) {
+ syncs.add(new PeriodicSync(account, providerName, item.first, item.second));
+ }
+ }
+ }
+ return syncs;
+ }
+
public void setMasterSyncAutomatically(boolean flag) {
boolean old;
synchronized (mAuthorities) {
@@ -817,7 +909,25 @@ public class SyncStorageEngine extends Handler {
return id;
}
- public void stopSyncEvent(long historyId, long elapsedTime, String resultMessage,
+ public static boolean equals(Bundle b1, Bundle b2) {
+ if (b1.size() != b2.size()) {
+ return false;
+ }
+ if (b1.isEmpty()) {
+ return true;
+ }
+ for (String key : b1.keySet()) {
+ if (!b2.containsKey(key)) {
+ return false;
+ }
+ if (!b1.get(key).equals(b2.get(key))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void stopSyncEvent(long historyId, Bundle extras, long elapsedTime, String resultMessage,
long downstreamActivity, long upstreamActivity) {
synchronized (mAuthorities) {
if (DEBUG) Log.v(TAG, "stopSyncEvent: historyId=" + historyId);
@@ -860,6 +970,17 @@ public class SyncStorageEngine extends Handler {
case SOURCE_SERVER:
status.numSourceServer++;
break;
+ case SOURCE_PERIODIC:
+ status.numSourcePeriodic++;
+ AuthorityInfo authority = mAuthorities.get(item.authorityId);
+ for (int periodicSyncIndex = 0;
+ periodicSyncIndex < authority.periodicSyncs.size();
+ periodicSyncIndex++) {
+ if (equals(extras, authority.periodicSyncs.get(periodicSyncIndex).first)) {
+ status.setPeriodicSyncTime(periodicSyncIndex, item.eventTime);
+ }
+ }
+ break;
}
boolean writeStatisticsNow = false;
@@ -948,11 +1069,27 @@ public class SyncStorageEngine extends Handler {
}
/**
+ * Return an array of the current authorities. Note
+ * that the objects inside the array are the real, live objects,
+ * so be careful what you do with them.
+ */
+ public ArrayList<AuthorityInfo> getAuthorities() {
+ synchronized (mAuthorities) {
+ final int N = mAuthorities.size();
+ ArrayList<AuthorityInfo> infos = new ArrayList<AuthorityInfo>(N);
+ for (int i=0; i<N; i++) {
+ infos.add(mAuthorities.valueAt(i));
+ }
+ return infos;
+ }
+ }
+
+ /**
* Returns the status that matches the authority and account.
*
* @param account the account we want to check
* @param authority the authority whose row should be selected
- * @return the SyncStatusInfo for the authority, or null if none exists
+ * @return the SyncStatusInfo for the authority
*/
public SyncStatusInfo getStatusByAccountAndAuthority(Account account, String authority) {
if (account == null || authority == null) {
@@ -1130,6 +1267,12 @@ public class SyncStorageEngine extends Handler {
return authority;
}
+ public SyncStatusInfo getOrCreateSyncStatus(AuthorityInfo authority) {
+ synchronized (mAuthorities) {
+ return getOrCreateSyncStatusLocked(authority.ident);
+ }
+ }
+
private SyncStatusInfo getOrCreateSyncStatusLocked(int authorityId) {
SyncStatusInfo status = mSyncStatus.get(authorityId);
if (status == null) {
@@ -1155,6 +1298,25 @@ public class SyncStorageEngine extends Handler {
}
/**
+ * public for testing
+ */
+ public void clearAndReadState() {
+ synchronized (mAuthorities) {
+ mAuthorities.clear();
+ mAccounts.clear();
+ mPendingOperations.clear();
+ mSyncStatus.clear();
+ mSyncHistory.clear();
+
+ readAccountInfoLocked();
+ readStatusLocked();
+ readPendingOperationsLocked();
+ readStatisticsLocked();
+ readLegacyAccountInfoLocked();
+ }
+ }
+
+ /**
* Read all account information back in to the initial engine state.
*/
private void readAccountInfoLocked() {
@@ -1175,59 +1337,23 @@ public class SyncStorageEngine extends Handler {
mMasterSyncAutomatically = listen == null
|| Boolean.parseBoolean(listen);
eventType = parser.next();
+ AuthorityInfo authority = null;
+ Pair<Bundle, Long> periodicSync = null;
do {
- if (eventType == XmlPullParser.START_TAG
- && parser.getDepth() == 2) {
+ if (eventType == XmlPullParser.START_TAG) {
tagName = parser.getName();
- if ("authority".equals(tagName)) {
- int id = -1;
- try {
- id = Integer.parseInt(parser.getAttributeValue(
- null, "id"));
- } catch (NumberFormatException e) {
- } catch (NullPointerException e) {
+ if (parser.getDepth() == 2) {
+ if ("authority".equals(tagName)) {
+ authority = parseAuthority(parser);
+ periodicSync = null;
+ }
+ } else if (parser.getDepth() == 3) {
+ if ("periodicSync".equals(tagName) && authority != null) {
+ periodicSync = parsePeriodicSync(parser, authority);
}
- if (id >= 0) {
- String accountName = parser.getAttributeValue(
- null, "account");
- String accountType = parser.getAttributeValue(
- null, "type");
- if (accountType == null) {
- accountType = "com.google";
- }
- String authorityName = parser.getAttributeValue(
- null, "authority");
- String enabled = parser.getAttributeValue(
- null, "enabled");
- String syncable = parser.getAttributeValue(null, "syncable");
- AuthorityInfo authority = mAuthorities.get(id);
- if (DEBUG_FILE) Log.v(TAG, "Adding authority: account="
- + accountName + " auth=" + authorityName
- + " enabled=" + enabled
- + " syncable=" + syncable);
- if (authority == null) {
- if (DEBUG_FILE) Log.v(TAG, "Creating entry");
- authority = getOrCreateAuthorityLocked(
- new Account(accountName, accountType),
- authorityName, id, false);
- }
- if (authority != null) {
- authority.enabled = enabled == null
- || Boolean.parseBoolean(enabled);
- if ("unknown".equals(syncable)) {
- authority.syncable = -1;
- } else {
- authority.syncable =
- (syncable == null || Boolean.parseBoolean(enabled))
- ? 1
- : 0;
- }
- } else {
- Log.w(TAG, "Failure adding authority: account="
- + accountName + " auth=" + authorityName
- + " enabled=" + enabled
- + " syncable=" + syncable);
- }
+ } else if (parser.getDepth() == 4 && periodicSync != null) {
+ if ("extra".equals(tagName)) {
+ parseExtra(parser, periodicSync);
}
}
}
@@ -1249,6 +1375,105 @@ public class SyncStorageEngine extends Handler {
}
}
+ private AuthorityInfo parseAuthority(XmlPullParser parser) {
+ AuthorityInfo authority = null;
+ int id = -1;
+ try {
+ id = Integer.parseInt(parser.getAttributeValue(
+ null, "id"));
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "error parsing the id of the authority", e);
+ } catch (NullPointerException e) {
+ Log.e(TAG, "the id of the authority is null", e);
+ }
+ if (id >= 0) {
+ String accountName = parser.getAttributeValue(null, "account");
+ String accountType = parser.getAttributeValue(null, "type");
+ if (accountType == null) {
+ accountType = "com.google";
+ }
+ String authorityName = parser.getAttributeValue(null, "authority");
+ String enabled = parser.getAttributeValue(null, "enabled");
+ String syncable = parser.getAttributeValue(null, "syncable");
+ authority = mAuthorities.get(id);
+ if (DEBUG_FILE) Log.v(TAG, "Adding authority: account="
+ + accountName + " auth=" + authorityName
+ + " enabled=" + enabled
+ + " syncable=" + syncable);
+ if (authority == null) {
+ if (DEBUG_FILE) Log.v(TAG, "Creating entry");
+ authority = getOrCreateAuthorityLocked(
+ new Account(accountName, accountType), authorityName, id, false);
+ // clear this since we will read these later on
+ authority.periodicSyncs.clear();
+ }
+ if (authority != null) {
+ authority.enabled = enabled == null || Boolean.parseBoolean(enabled);
+ if ("unknown".equals(syncable)) {
+ authority.syncable = -1;
+ } else {
+ authority.syncable =
+ (syncable == null || Boolean.parseBoolean(enabled)) ? 1 : 0;
+ }
+ } else {
+ Log.w(TAG, "Failure adding authority: account="
+ + accountName + " auth=" + authorityName
+ + " enabled=" + enabled
+ + " syncable=" + syncable);
+ }
+ }
+
+ return authority;
+ }
+
+ private Pair<Bundle, Long> parsePeriodicSync(XmlPullParser parser, AuthorityInfo authority) {
+ Bundle extras = new Bundle();
+ String periodValue = parser.getAttributeValue(null, "period");
+ final long period;
+ try {
+ period = Long.parseLong(periodValue);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "error parsing the period of a periodic sync", e);
+ return null;
+ } catch (NullPointerException e) {
+ Log.e(TAG, "the period of a periodic sync is null", e);
+ return null;
+ }
+ final Pair<Bundle, Long> periodicSync = Pair.create(extras, period);
+ authority.periodicSyncs.add(periodicSync);
+ return periodicSync;
+ }
+
+ private void parseExtra(XmlPullParser parser, Pair<Bundle, Long> periodicSync) {
+ final Bundle extras = periodicSync.first;
+ String name = parser.getAttributeValue(null, "name");
+ String type = parser.getAttributeValue(null, "type");
+ String value1 = parser.getAttributeValue(null, "value1");
+ String value2 = parser.getAttributeValue(null, "value2");
+
+ try {
+ if ("long".equals(type)) {
+ extras.putLong(name, Long.parseLong(value1));
+ } else if ("integer".equals(type)) {
+ extras.putInt(name, Integer.parseInt(value1));
+ } else if ("double".equals(type)) {
+ extras.putDouble(name, Double.parseDouble(value1));
+ } else if ("float".equals(type)) {
+ extras.putFloat(name, Float.parseFloat(value1));
+ } else if ("boolean".equals(type)) {
+ extras.putBoolean(name, Boolean.parseBoolean(value1));
+ } else if ("string".equals(type)) {
+ extras.putString(name, value1);
+ } else if ("account".equals(type)) {
+ extras.putParcelable(name, new Account(value1, value2));
+ }
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "error parsing bundle value", e);
+ } catch (NullPointerException e) {
+ Log.e(TAG, "error parsing bundle value", e);
+ }
+ }
+
/**
* Write all account information to the account file.
*/
@@ -1284,6 +1509,41 @@ public class SyncStorageEngine extends Handler {
} else if (authority.syncable == 0) {
out.attribute(null, "syncable", "false");
}
+ for (Pair<Bundle, Long> periodicSync : authority.periodicSyncs) {
+ out.startTag(null, "periodicSync");
+ out.attribute(null, "period", Long.toString(periodicSync.second));
+ final Bundle extras = periodicSync.first;
+ for (String key : extras.keySet()) {
+ out.startTag(null, "extra");
+ out.attribute(null, "name", key);
+ final Object value = extras.get(key);
+ if (value instanceof Long) {
+ out.attribute(null, "type", "long");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Integer) {
+ out.attribute(null, "type", "integer");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Boolean) {
+ out.attribute(null, "type", "boolean");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Float) {
+ out.attribute(null, "type", "float");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Double) {
+ out.attribute(null, "type", "double");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof String) {
+ out.attribute(null, "type", "string");
+ out.attribute(null, "value1", value.toString());
+ } else if (value instanceof Account) {
+ out.attribute(null, "type", "account");
+ out.attribute(null, "value1", ((Account)value).name);
+ out.attribute(null, "value2", ((Account)value).type);
+ }
+ out.endTag(null, "extra");
+ }
+ out.endTag(null, "periodicSync");
+ }
out.endTag(null, "authority");
}
@@ -1389,6 +1649,7 @@ public class SyncStorageEngine extends Handler {
st.numSourcePoll = getIntColumn(c, "numSourcePoll");
st.numSourceServer = getIntColumn(c, "numSourceServer");
st.numSourceUser = getIntColumn(c, "numSourceUser");
+ st.numSourcePeriodic = 0;
st.lastSuccessSource = getIntColumn(c, "lastSuccessSource");
st.lastSuccessTime = getLongColumn(c, "lastSuccessTime");
st.lastFailureSource = getIntColumn(c, "lastFailureSource");