SyncStorageEnginepublic class SyncStorageEngine extends Object ContentProvider that tracks the sync data and overall sync
history on the device. |
Fields Summary |
---|
private static final String | TAG | private static final String | DATABASE_NAME | private static final int | DATABASE_VERSION | private static final int | STATS | private static final int | STATS_ID | private static final int | HISTORY | private static final int | HISTORY_ID | private static final int | SETTINGS | private static final int | PENDING | private static final int | ACTIVE | private static final int | STATUS | private static final UriMatcher | sURLMatcher | private static final HashMap | HISTORY_PROJECTION_MAP | private static final HashMap | PENDING_PROJECTION_MAP | private static final HashMap | ACTIVE_PROJECTION_MAP | private static final HashMap | STATUS_PROJECTION_MAP | private final Context | mContext | private final android.database.sqlite.SQLiteOpenHelper | mOpenHelper | private static SyncStorageEngine | sSyncStorageEngine | private static final String[] | STATS_ACCOUNT_PROJECTION | private static final int | MAX_HISTORY_EVENTS_TO_KEEP | private static final String | SELECT_INITIAL_FAILURE_TIME_QUERY_STRING | static final long | MILLIS_IN_4WEEKS |
Constructors Summary |
---|
private SyncStorageEngine(Context context)
mContext = context;
mOpenHelper = new SyncStorageEngine.DatabaseHelper(context);
sSyncStorageEngine = this;
|
Methods Summary |
---|
private static void | checkCaller(boolean callerIsTheProvider, int match)
if (callerIsTheProvider && match != SETTINGS) {
throw new UnsupportedOperationException(
"only the settings are modifiable via the ContentProvider interface");
}
| int | clearPending()
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
db.beginTransaction();
try {
int numChanges = db.delete("pending", null, null /* no where args */);
if (numChanges > 0) {
db.execSQL("UPDATE status SET pending=0");
mContext.getContentResolver().notifyChange(Sync.Pending.CONTENT_URI,
null /* no observer initiated this change */);
mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI,
null /* no observer initiated this change */);
}
db.setTransactionSuccessful();
return numChanges;
} finally {
db.endTransaction();
}
| private long | createStatsRowIfNecessary(java.lang.String account, java.lang.String authority)
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
StringBuilder where = new StringBuilder();
where.append(Sync.Stats.ACCOUNT + "= ?");
where.append(" and " + Sync.Stats.AUTHORITY + "= ?");
Cursor cursor = query(Sync.Stats.CONTENT_URI,
Sync.Stats.SYNC_STATS_PROJECTION,
where.toString(), new String[] { account, authority },
null /* order */);
try {
long id;
if (cursor.moveToFirst()) {
id = cursor.getLong(cursor.getColumnIndexOrThrow(Sync.Stats._ID));
} else {
ContentValues values = new ContentValues();
values.put(Sync.Stats.ACCOUNT, account);
values.put(Sync.Stats.AUTHORITY, authority);
id = db.insert("stats", null, values);
}
return id;
} finally {
cursor.close();
}
| private void | createStatusRowIfNecessary(long statsId)
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
boolean statusExists = 0 != DatabaseUtils.longForQuery(db,
"SELECT count(*) FROM status WHERE stats_id=" + statsId, null);
if (!statusExists) {
ContentValues values = new ContentValues();
values.put("stats_id", statsId);
db.insert("status", null, values);
}
| public int | delete(boolean callerIsTheProvider, android.net.Uri url, java.lang.String where, java.lang.String[] whereArgs)Implements the {@link ContentProvider#delete} method
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int match = sURLMatcher.match(url);
int numRows;
switch (match) {
case SETTINGS:
mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
"no permission to write the sync settings");
numRows = db.delete("settings", where, whereArgs);
break;
default:
throw new UnsupportedOperationException("Cannot delete URL: " + url);
}
if (numRows > 0) {
mContext.getContentResolver().notifyChange(url, null /* observer */);
}
return numRows;
| int | deleteFromPending(long rowId)
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
db.beginTransaction();
try {
String account;
String authority;
Cursor c = db.query("pending",
new String[]{Sync.Pending.ACCOUNT, Sync.Pending.AUTHORITY},
"_id=" + rowId, null, null, null, null);
try {
if (c.getCount() != 1) {
return 0;
}
c.moveToNext();
account = c.getString(0);
authority = c.getString(1);
} finally {
c.close();
}
db.delete("pending", "_id=" + rowId, null /* no where args */);
final String[] accountAuthorityWhereArgs = new String[]{account, authority};
boolean isPending = 0 < DatabaseUtils.longForQuery(db,
"SELECT COUNT(*) FROM PENDING WHERE account=? AND authority=?",
accountAuthorityWhereArgs);
if (!isPending) {
long statsId = createStatsRowIfNecessary(account, authority);
db.execSQL("UPDATE status SET pending=0 WHERE stats_id=" + statsId);
}
db.setTransactionSuccessful();
mContext.getContentResolver().notifyChange(Sync.Pending.CONTENT_URI,
null /* no observer initiated this change */);
if (!isPending) {
mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI,
null /* no observer initiated this change */);
}
return 1;
} finally {
db.endTransaction();
}
| protected void | doDatabaseCleanup(java.lang.String[] accounts)
HashSet<String> currentAccounts = new HashSet<String>();
for (String account : accounts) currentAccounts.add(account);
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
Cursor cursor = db.query("stats", STATS_ACCOUNT_PROJECTION,
null /* where */, null /* where args */, Sync.Stats.ACCOUNT,
null /* having */, null /* order by */);
try {
while (cursor.moveToNext()) {
String account = cursor.getString(0);
if (TextUtils.isEmpty(account)) {
continue;
}
if (!currentAccounts.contains(account)) {
String where = Sync.Stats.ACCOUNT + "=?";
int numDeleted;
numDeleted = db.delete("stats", where, new String[]{account});
if (Config.LOGD) {
Log.d(TAG, "deleted " + numDeleted
+ " records from stats table"
+ " for account " + account);
}
}
}
} finally {
cursor.close();
}
| public long | getInitialSyncFailureTime()If sync is failing for any of the provider/accounts then determine the time at which it
started failing and return the earliest time over all the provider/accounts. If none are
failing then return 0.
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
// Join the settings for a provider with the status so that we can easily
// check if each provider is enabled for syncing. We also join in the overall
// enabled flag ("listen_for_tickles") to each row so that we don't need to
// make a separate DB lookup to access it.
Cursor c = db.rawQuery(""
+ "SELECT initialFailureTime, s1.value, s2.value "
+ "FROM status "
+ "LEFT JOIN stats ON status.stats_id=stats._id "
+ "LEFT JOIN settings as s1 ON 'sync_provider_' || authority=s1.name "
+ "LEFT JOIN settings as s2 ON s2.name='listen_for_tickles' "
+ "where initialFailureTime is not null "
+ " AND lastFailureMesg!=" + Sync.History.ERROR_TOO_MANY_DELETIONS
+ " AND lastFailureMesg!=" + Sync.History.ERROR_AUTHENTICATION
+ " AND lastFailureMesg!=" + Sync.History.ERROR_SYNC_ALREADY_IN_PROGRESS
+ " AND authority!='subscribedfeeds' "
+ " ORDER BY initialFailureTime", null);
try {
while (c.moveToNext()) {
// these settings default to true, so if they are null treat them as enabled
final String providerEnabledString = c.getString(1);
if (providerEnabledString != null && !Boolean.parseBoolean(providerEnabledString)) {
continue;
}
final String allEnabledString = c.getString(2);
if (allEnabledString != null && !Boolean.parseBoolean(allEnabledString)) {
continue;
}
return c.getLong(0);
}
} finally {
c.close();
}
return 0;
| public android.database.Cursor | getPendingSyncsCursor(java.lang.String[] projection)Returns a cursor over all the pending syncs in no particular order. This cursor is not
"live", in that if changes are made to the pending table any observers on this cursor
will not be notified.
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
return db.query("pending", projection, null, null, null, null, null);
| public static android.content.SyncStorageEngine | getSingleton()
if (sSyncStorageEngine == null) {
throw new IllegalStateException("not initialized");
}
return sSyncStorageEngine;
| public java.lang.String | getType(android.net.Uri url)Implements the {@link ContentProvider#getType} method
int match = sURLMatcher.match(url);
switch (match) {
case SETTINGS:
return "vnd.android.cursor.dir/sync-settings";
default:
throw new IllegalArgumentException("Unknown URL");
}
| public static void | init(Context context)
if (sSyncStorageEngine != null) {
throw new IllegalStateException("already initialized");
}
sSyncStorageEngine = new SyncStorageEngine(context);
| public android.net.Uri | insert(boolean callerIsTheProvider, android.net.Uri url, ContentValues values)Implements the {@link ContentProvider#insert} method
String table;
long rowID;
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
final int match = sURLMatcher.match(url);
checkCaller(callerIsTheProvider, match);
switch (match) {
case SETTINGS:
mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS,
"no permission to write the sync settings");
table = "settings";
rowID = db.replace(table, null, values);
break;
default:
throw new IllegalArgumentException("Unknown URL " + url);
}
if (rowID > 0) {
mContext.getContentResolver().notifyChange(url, null /* observer */);
return Uri.parse("content://sync/" + table + "/" + rowID);
}
return null;
| protected android.net.Uri | insertIntoPending(ContentValues values)
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
try {
db.beginTransaction();
long rowId = db.insert("pending", Sync.Pending.ACCOUNT, values);
if (rowId < 0) return null;
String account = values.getAsString(Sync.Pending.ACCOUNT);
String authority = values.getAsString(Sync.Pending.AUTHORITY);
long statsId = createStatsRowIfNecessary(account, authority);
createStatusRowIfNecessary(statsId);
values.clear();
values.put(Sync.Status.PENDING, 1);
int numUpdatesStatus = db.update("status", values, "stats_id=" + statsId, null);
db.setTransactionSuccessful();
mContext.getContentResolver().notifyChange(Sync.Pending.CONTENT_URI,
null /* no observer initiated this change */);
if (numUpdatesStatus > 0) {
mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI,
null /* no observer initiated this change */);
}
return ContentUris.withAppendedId(Sync.Pending.CONTENT_URI, rowId);
} finally {
db.endTransaction();
}
| public long | insertStartSyncEvent(java.lang.String account, java.lang.String authority, long now, int source)
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
long statsId = createStatsRowIfNecessary(account, authority);
purgeOldHistoryEvents(now);
ContentValues values = new ContentValues();
values.put(Sync.History.STATS_ID, statsId);
values.put(Sync.History.EVENT_TIME, now);
values.put(Sync.History.SOURCE, source);
values.put(Sync.History.EVENT, Sync.History.EVENT_START);
long rowId = db.insert("history", null, values);
mContext.getContentResolver().notifyChange(Sync.History.CONTENT_URI, null /* observer */);
mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI, null /* observer */);
return rowId;
| public static android.content.SyncStorageEngine | newTestInstance(Context context)
return new SyncStorageEngine(context);
| private boolean | purgeOldHistoryEvents(long now)
// remove events that are older than MILLIS_IN_4WEEKS
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int numDeletes = db.delete("history", "eventTime<" + (now - MILLIS_IN_4WEEKS), null);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
if (numDeletes > 0) {
Log.v(TAG, "deleted " + numDeletes + " old event(s) from the sync history");
}
}
// keep only the last MAX_HISTORY_EVENTS_TO_KEEP history events
numDeletes += db.delete("history", "eventTime < (select min(eventTime) from "
+ "(select eventTime from history order by eventTime desc limit ?))",
new String[]{String.valueOf(MAX_HISTORY_EVENTS_TO_KEEP)});
return numDeletes > 0;
| public android.database.Cursor | query(android.net.Uri url, java.lang.String[] projectionIn, java.lang.String selection, java.lang.String[] selectionArgs, java.lang.String sort)Implements the {@link ContentProvider#query} method
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
// Generate the body of the query
int match = sURLMatcher.match(url);
String groupBy = null;
switch (match) {
case STATS:
qb.setTables("stats");
break;
case STATS_ID:
qb.setTables("stats");
qb.appendWhere("_id=");
qb.appendWhere(url.getPathSegments().get(1));
break;
case HISTORY:
// join the stats and history tables, so the caller can get
// the account and authority information as part of this query.
qb.setTables("stats, history");
qb.setProjectionMap(HISTORY_PROJECTION_MAP);
qb.appendWhere("stats._id = history.stats_id");
break;
case ACTIVE:
qb.setTables("active");
qb.setProjectionMap(ACTIVE_PROJECTION_MAP);
qb.appendWhere("account is not null");
break;
case PENDING:
qb.setTables("pending");
qb.setProjectionMap(PENDING_PROJECTION_MAP);
groupBy = "account, authority";
break;
case STATUS:
// join the stats and status tables, so the caller can get
// the account and authority information as part of this query.
qb.setTables("stats, status");
qb.setProjectionMap(STATUS_PROJECTION_MAP);
qb.appendWhere("stats._id = status.stats_id");
break;
case HISTORY_ID:
// join the stats and history tables, so the caller can get
// the account and authority information as part of this query.
qb.setTables("stats, history");
qb.setProjectionMap(HISTORY_PROJECTION_MAP);
qb.appendWhere("stats._id = history.stats_id");
qb.appendWhere("AND history._id=");
qb.appendWhere(url.getPathSegments().get(1));
break;
case SETTINGS:
qb.setTables("settings");
break;
default:
throw new IllegalArgumentException("Unknown URL " + url);
}
if (match == SETTINGS) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS,
"no permission to read the sync settings");
} else {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS,
"no permission to read the sync stats");
}
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
Cursor c = qb.query(db, projectionIn, selection, selectionArgs, groupBy, null, sort);
c.setNotificationUri(mContext.getContentResolver(), url);
return c;
| protected void | setActiveSync(SyncManager.ActiveSyncContext activeSyncContext)
if (activeSyncContext != null) {
updateActiveSync(activeSyncContext.mSyncOperation.account,
activeSyncContext.mSyncOperation.authority, activeSyncContext.mStartTime);
} else {
// we indicate that the sync is not active by passing null for all the parameters
updateActiveSync(null, null, null);
}
| public void | stopSyncEvent(long historyId, long elapsedTime, java.lang.String resultMessage, long downstreamActivity, long upstreamActivity)
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
db.beginTransaction();
try {
ContentValues values = new ContentValues();
values.put(Sync.History.ELAPSED_TIME, elapsedTime);
values.put(Sync.History.EVENT, Sync.History.EVENT_STOP);
values.put(Sync.History.MESG, resultMessage);
values.put(Sync.History.DOWNSTREAM_ACTIVITY, downstreamActivity);
values.put(Sync.History.UPSTREAM_ACTIVITY, upstreamActivity);
int count = db.update("history", values, "_id=?",
new String[]{Long.toString(historyId)});
// We think that count should always be 1 but don't want to change this until after
// launch.
if (count > 0) {
int source = (int) DatabaseUtils.longForQuery(db,
"SELECT source FROM history WHERE _id=" + historyId, null);
long eventTime = DatabaseUtils.longForQuery(db,
"SELECT eventTime FROM history WHERE _id=" + historyId, null);
long statsId = DatabaseUtils.longForQuery(db,
"SELECT stats_id FROM history WHERE _id=" + historyId, null);
createStatusRowIfNecessary(statsId);
// update the status table to reflect this sync
StringBuilder sb = new StringBuilder();
ArrayList<String> bindArgs = new ArrayList<String>();
sb.append("UPDATE status SET");
sb.append(" numSyncs=numSyncs+1");
sb.append(", totalElapsedTime=totalElapsedTime+" + elapsedTime);
switch (source) {
case Sync.History.SOURCE_LOCAL:
sb.append(", numSourceLocal=numSourceLocal+1");
break;
case Sync.History.SOURCE_POLL:
sb.append(", numSourcePoll=numSourcePoll+1");
break;
case Sync.History.SOURCE_USER:
sb.append(", numSourceUser=numSourceUser+1");
break;
case Sync.History.SOURCE_SERVER:
sb.append(", numSourceServer=numSourceServer+1");
break;
}
final String statsIdString = String.valueOf(statsId);
final long lastSyncTime = (eventTime + elapsedTime);
if (Sync.History.MESG_SUCCESS.equals(resultMessage)) {
// - if successful, update the successful columns
sb.append(", lastSuccessTime=" + lastSyncTime);
sb.append(", lastSuccessSource=" + source);
sb.append(", lastFailureTime=null");
sb.append(", lastFailureSource=null");
sb.append(", lastFailureMesg=null");
sb.append(", initialFailureTime=null");
} else if (!Sync.History.MESG_CANCELED.equals(resultMessage)) {
sb.append(", lastFailureTime=" + lastSyncTime);
sb.append(", lastFailureSource=" + source);
sb.append(", lastFailureMesg=?");
bindArgs.add(resultMessage);
long initialFailureTime = DatabaseUtils.longForQuery(db,
SELECT_INITIAL_FAILURE_TIME_QUERY_STRING,
new String[]{statsIdString, String.valueOf(lastSyncTime)});
sb.append(", initialFailureTime=" + initialFailureTime);
}
sb.append(" WHERE stats_id=?");
bindArgs.add(statsIdString);
db.execSQL(sb.toString(), bindArgs.toArray());
db.setTransactionSuccessful();
mContext.getContentResolver().notifyChange(Sync.History.CONTENT_URI,
null /* observer */);
mContext.getContentResolver().notifyChange(Sync.Status.CONTENT_URI,
null /* observer */);
}
} finally {
db.endTransaction();
}
| public int | update(boolean callerIsTheProvider, android.net.Uri url, ContentValues initialValues, java.lang.String where, java.lang.String[] whereArgs)Implements the {@link ContentProvider#update} method
switch (sURLMatcher.match(url)) {
case SETTINGS:
throw new UnsupportedOperationException("updating url " + url
+ " is not allowed, use insert instead");
default:
throw new UnsupportedOperationException("Cannot update URL: " + url);
}
| private int | updateActiveSync(java.lang.String account, java.lang.String authority, java.lang.Long startTime)
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("account", account);
values.put("authority", authority);
values.put("startTime", startTime);
int numChanges = db.update("active", values, null, null);
if (numChanges > 0) {
mContext.getContentResolver().notifyChange(Sync.Active.CONTENT_URI,
null /* this change wasn't made through an observer */);
}
return numChanges;
|
|