diff options
Diffstat (limited to 'core/java/android/provider/Sync.java')
-rw-r--r-- | core/java/android/provider/Sync.java | 649 |
1 files changed, 649 insertions, 0 deletions
diff --git a/core/java/android/provider/Sync.java b/core/java/android/provider/Sync.java new file mode 100644 index 0000000..c9bde0e --- /dev/null +++ b/core/java/android/provider/Sync.java @@ -0,0 +1,649 @@ +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.provider; + +import android.content.ContentQueryMap; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.os.Handler; +import android.accounts.Account; +import android.text.TextUtils; + +import java.util.Map; + +/** + * The Sync provider stores information used in managing the syncing of the device, + * including the history and pending syncs. + * + * @hide + */ +public final class Sync { + // utility class + private Sync() {} + + /** + * The content url for this provider. + */ + public static final Uri CONTENT_URI = Uri.parse("content://sync"); + + /** + * Columns from the stats table. + */ + public interface StatsColumns { + /** + * The sync account. + * <P>Type: TEXT</P> + */ + public static final String ACCOUNT = "account"; + + /** + * The sync account type. + * <P>Type: TEXT</P> + */ + public static final String ACCOUNT_TYPE = "account_type"; + + /** + * The content authority (contacts, calendar, etc.). + * <P>Type: TEXT</P> + */ + public static final String AUTHORITY = "authority"; + } + + /** + * Provides constants and utility methods to access and use the stats table. + */ + public static final class Stats implements BaseColumns, StatsColumns { + + // utility class + private Stats() {} + + /** + * The content url for this table. + */ + public static final Uri CONTENT_URI = + Uri.parse("content://sync/stats"); + + /** Projection for the _id column in the stats table. */ + public static final String[] SYNC_STATS_PROJECTION = {_ID}; + } + + /** + * Columns from the history table. + */ + public interface HistoryColumns { + /** + * The ID of the stats row corresponding to this event. + * <P>Type: INTEGER</P> + */ + public static final String STATS_ID = "stats_id"; + + /** + * The source of the sync event (LOCAL, POLL, USER, SERVER). + * <P>Type: INTEGER</P> + */ + public static final String SOURCE = "source"; + + /** + * The type of sync event (START, STOP). + * <P>Type: INTEGER</P> + */ + public static final String EVENT = "event"; + + /** + * The time of the event. + * <P>Type: INTEGER</P> + */ + public static final String EVENT_TIME = "eventTime"; + + /** + * How long this event took. This is only valid if the EVENT is EVENT_STOP. + * <P>Type: INTEGER</P> + */ + public static final String ELAPSED_TIME = "elapsedTime"; + + /** + * Any additional message associated with this event. + * <P>Type: TEXT</P> + */ + public static final String MESG = "mesg"; + + /** + * How much activity was performed sending data to the server. This is sync adapter + * specific, but usually is something like how many record update/insert/delete attempts + * were carried out. This is only valid if the EVENT is EVENT_STOP. + * <P>Type: INTEGER</P> + */ + public static final String UPSTREAM_ACTIVITY = "upstreamActivity"; + + /** + * How much activity was performed while receiving data from the server. + * This is sync adapter specific, but usually is something like how many + * records were received from the server. This is only valid if the + * EVENT is EVENT_STOP. + * <P>Type: INTEGER</P> + */ + public static final String DOWNSTREAM_ACTIVITY = "downstreamActivity"; + } + + /** + * Columns from the history table. + */ + public interface StatusColumns { + /** + * How many syncs were completed for this account and authority. + * <P>Type: INTEGER</P> + */ + public static final String NUM_SYNCS = "numSyncs"; + + /** + * How long all the events for this account and authority took. + * <P>Type: INTEGER</P> + */ + public static final String TOTAL_ELAPSED_TIME = "totalElapsedTime"; + + /** + * The number of syncs with SOURCE_POLL. + * <P>Type: INTEGER</P> + */ + public static final String NUM_SOURCE_POLL = "numSourcePoll"; + + /** + * The number of syncs with SOURCE_SERVER. + * <P>Type: INTEGER</P> + */ + public static final String NUM_SOURCE_SERVER = "numSourceServer"; + + /** + * The number of syncs with SOURCE_LOCAL. + * <P>Type: INTEGER</P> + */ + public static final String NUM_SOURCE_LOCAL = "numSourceLocal"; + + /** + * The number of syncs with SOURCE_USER. + * <P>Type: INTEGER</P> + */ + public static final String NUM_SOURCE_USER = "numSourceUser"; + + /** + * The time in ms that the last successful sync ended. Will be null if + * there are no successful syncs. A successful sync is defined as one having + * MESG=MESG_SUCCESS. + * <P>Type: INTEGER</P> + */ + public static final String LAST_SUCCESS_TIME = "lastSuccessTime"; + + /** + * The SOURCE of the last successful sync. Will be null if + * there are no successful syncs. A successful sync is defined + * as one having MESG=MESG_SUCCESS. + * <P>Type: INTEGER</P> + */ + public static final String LAST_SUCCESS_SOURCE = "lastSuccessSource"; + + /** + * The end time in ms of the last sync that failed since the last successful sync. + * Will be null if there are no syncs or if the last one succeeded. A failed + * sync is defined as one where MESG isn't MESG_SUCCESS or MESG_CANCELED. + * <P>Type: INTEGER</P> + */ + public static final String LAST_FAILURE_TIME = "lastFailureTime"; + + /** + * The SOURCE of the last sync that failed since the last successful sync. + * Will be null if there are no syncs or if the last one succeeded. A failed + * sync is defined as one where MESG isn't MESG_SUCCESS or MESG_CANCELED. + * <P>Type: INTEGER</P> + */ + public static final String LAST_FAILURE_SOURCE = "lastFailureSource"; + + /** + * The MESG of the last sync that failed since the last successful sync. + * Will be null if there are no syncs or if the last one succeeded. A failed + * sync is defined as one where MESG isn't MESG_SUCCESS or MESG_CANCELED. + * <P>Type: STRING</P> + */ + public static final String LAST_FAILURE_MESG = "lastFailureMesg"; + + /** + * Is set to 1 if a sync is pending, 0 if not. + * <P>Type: INTEGER</P> + */ + public static final String PENDING = "pending"; + } + + /** + * Provides constants and utility methods to access and use the history + * table. + */ + public static class History implements BaseColumns, + StatsColumns, + HistoryColumns { + + /** + * The content url for this table. + */ + public static final Uri CONTENT_URI = + Uri.parse("content://sync/history"); + + /** Enum value for a sync start event. */ + public static final int EVENT_START = 0; + + /** Enum value for a sync stop event. */ + public static final int EVENT_STOP = 1; + + // TODO: i18n -- grab these out of resources. + /** String names for the sync event types. */ + public static final String[] EVENTS = { "START", "STOP" }; + + /** Enum value for a server-initiated sync. */ + public static final int SOURCE_SERVER = 0; + + /** Enum value for a local-initiated sync. */ + public static final int SOURCE_LOCAL = 1; + /** + * Enum value for a poll-based sync (e.g., upon connection to + * network) + */ + public static final int SOURCE_POLL = 2; + + /** Enum value for a user-initiated sync. */ + public static final int SOURCE_USER = 3; + + // TODO: i18n -- grab these out of resources. + /** String names for the sync source types. */ + public static final String[] SOURCES = { "SERVER", + "LOCAL", + "POLL", + "USER" }; + + // Error types + public static final int ERROR_SYNC_ALREADY_IN_PROGRESS = 1; + public static final int ERROR_AUTHENTICATION = 2; + public static final int ERROR_IO = 3; + public static final int ERROR_PARSE = 4; + public static final int ERROR_CONFLICT = 5; + public static final int ERROR_TOO_MANY_DELETIONS = 6; + public static final int ERROR_TOO_MANY_RETRIES = 7; + public static final int ERROR_INTERNAL = 8; + + // The MESG column will contain one of these or one of the Error types. + public static final String MESG_SUCCESS = "success"; + public static final String MESG_CANCELED = "canceled"; + + private static final String FINISHED_SINCE_WHERE_CLAUSE = EVENT + "=" + EVENT_STOP + + " AND " + EVENT_TIME + ">? AND " + ACCOUNT + "=? AND " + ACCOUNT_TYPE + "=?" + + " AND " + AUTHORITY + "=?"; + + public static String mesgToString(String mesg) { + if (MESG_SUCCESS.equals(mesg)) return mesg; + if (MESG_CANCELED.equals(mesg)) return mesg; + switch (Integer.parseInt(mesg)) { + case ERROR_SYNC_ALREADY_IN_PROGRESS: return "already in progress"; + case ERROR_AUTHENTICATION: return "bad authentication"; + case ERROR_IO: return "network error"; + case ERROR_PARSE: return "parse error"; + case ERROR_CONFLICT: return "conflict detected"; + case ERROR_TOO_MANY_DELETIONS: return "too many deletions"; + case ERROR_TOO_MANY_RETRIES: return "too many retries"; + case ERROR_INTERNAL: return "internal error"; + default: return "unknown error"; + } + } + + // utility class + private History() {} + + /** + * returns a cursor that queries the sync history in descending event time order + * @param contentResolver the ContentResolver to use for the query + * @return the cursor on the History table + */ + public static Cursor query(ContentResolver contentResolver) { + return contentResolver.query(CONTENT_URI, null, null, null, EVENT_TIME + " desc"); + } + + public static boolean hasNewerSyncFinished(ContentResolver contentResolver, + Account account, String authority, long when) { + Cursor c = contentResolver.query(CONTENT_URI, new String[]{_ID}, + FINISHED_SINCE_WHERE_CLAUSE, + new String[]{Long.toString(when), account.mName, account.mType, authority}, + null); + try { + return c.getCount() > 0; + } finally { + c.close(); + } + } + } + + /** + * Provides constants and utility methods to access and use the authority history + * table, which contains information about syncs aggregated by account and authority. + * All the HistoryColumns except for EVENT are present, plus the AuthorityHistoryColumns. + */ + public static class Status extends History implements StatusColumns { + + /** + * The content url for this table. + */ + public static final Uri CONTENT_URI = Uri.parse("content://sync/status"); + + // utility class + private Status() {} + + /** + * returns a cursor that queries the authority sync history in descending event order of + * ACCOUNT, AUTHORITY + * @param contentResolver the ContentResolver to use for the query + * @return the cursor on the AuthorityHistory table + */ + public static Cursor query(ContentResolver contentResolver) { + return contentResolver.query(CONTENT_URI, null, null, null, + ACCOUNT_TYPE + "," + ACCOUNT + "," + AUTHORITY); + } + + public static class QueryMap extends ContentQueryMap { + public QueryMap(ContentResolver contentResolver, + boolean keepUpdated, + Handler handlerForUpdateNotifications) { + super(contentResolver.query(CONTENT_URI, null, null, null, null), + _ID, keepUpdated, handlerForUpdateNotifications); + } + + public ContentValues get(Account account, String authority) { + Map<String, ContentValues> rows = getRows(); + for (ContentValues values : rows.values()) { + if (values.getAsString(ACCOUNT).equals(account.mName) + && values.getAsString(ACCOUNT_TYPE).equals(account.mType) + && values.getAsString(AUTHORITY).equals(authority)) { + return values; + } + } + return null; + } + } + } + + /** + * Provides constants and utility methods to access and use the pending syncs table + */ + public static final class Pending implements BaseColumns, + StatsColumns { + + /** + * The content url for this table. + */ + public static final Uri CONTENT_URI = Uri.parse("content://sync/pending"); + + // utility class + private Pending() {} + + public static class QueryMap extends ContentQueryMap { + public QueryMap(ContentResolver contentResolver, boolean keepUpdated, + Handler handlerForUpdateNotifications) { + super(contentResolver.query(CONTENT_URI, null, null, null, null), _ID, keepUpdated, + handlerForUpdateNotifications); + } + + public boolean isPending(Account account, String authority) { + Map<String, ContentValues> rows = getRows(); + for (ContentValues values : rows.values()) { + if (values.getAsString(ACCOUNT).equals(account.mName) + && values.getAsString(ACCOUNT_TYPE).equals(account.mType) + && values.getAsString(AUTHORITY).equals(authority)) { + return true; + } + } + return false; + } + } + } + + /** + * Columns from the history table. + */ + public interface ActiveColumns { + /** + * The wallclock time of when the active sync started. + * <P>Type: INTEGER</P> + */ + public static final String START_TIME = "startTime"; + } + + /** + * Provides constants and utility methods to access and use the pending syncs table + */ + public static final class Active implements BaseColumns, + StatsColumns, + ActiveColumns { + + /** + * The content url for this table. + */ + public static final Uri CONTENT_URI = Uri.parse("content://sync/active"); + + // utility class + private Active() {} + + public static class QueryMap extends ContentQueryMap { + public QueryMap(ContentResolver contentResolver, boolean keepUpdated, + Handler handlerForUpdateNotifications) { + super(contentResolver.query(CONTENT_URI, null, null, null, null), _ID, keepUpdated, + handlerForUpdateNotifications); + } + + public ContentValues getActiveSyncInfo() { + Map<String, ContentValues> rows = getRows(); + for (ContentValues values : rows.values()) { + return values; + } + return null; + } + + public Account getSyncingAccount() { + ContentValues values = getActiveSyncInfo(); + if (values == null || TextUtils.isEmpty(values.getAsString(ACCOUNT))) { + return null; + } + return new Account(values.getAsString(ACCOUNT), values.getAsString(ACCOUNT_TYPE)); + } + + public String getSyncingAuthority() { + ContentValues values = getActiveSyncInfo(); + return (values == null) ? null : values.getAsString(AUTHORITY); + } + + public long getSyncStartTime() { + ContentValues values = getActiveSyncInfo(); + return (values == null) ? -1 : values.getAsLong(START_TIME); + } + } + } + + /** + * Columns in the settings table, which holds key/value pairs of settings. + */ + public interface SettingsColumns { + /** + * The key of the setting + * <P>Type: TEXT</P> + */ + public static final String KEY = "name"; + + /** + * The value of the settings + * <P>Type: TEXT</P> + */ + public static final String VALUE = "value"; + } + + /** + * Provides constants and utility methods to access and use the settings + * table. + */ + public static final class Settings implements BaseColumns, SettingsColumns { + /** + * The Uri of the settings table. This table behaves a little differently than + * normal tables. Updates are not allowed, only inserts, and inserts cause a replace + * to be performed, which first deletes the row if it is already present. + */ + public static final Uri CONTENT_URI = Uri.parse("content://sync/settings"); + + /** controls whether or not the device listens for sync tickles */ + public static final String SETTING_LISTEN_FOR_TICKLES = "listen_for_tickles"; + + /** controls whether or not the individual provider is synced when tickles are received */ + public static final String SETTING_SYNC_PROVIDER_PREFIX = "sync_provider_"; + + /** query column project */ + private static final String[] PROJECTION = { KEY, VALUE }; + + /** + * Convenience function for updating a single settings value as a + * boolean. This will either create a new entry in the table if the + * given name does not exist, or modify the value of the existing row + * with that name. Note that internally setting values are always + * stored as strings, so this function converts the given value to a + * string before storing it. + * + * @param contentResolver the ContentResolver to use to access the settings table + * @param name The name of the setting to modify. + * @param val The new value for the setting. + */ + static private void putBoolean(ContentResolver contentResolver, String name, boolean val) { + ContentValues values = new ContentValues(); + values.put(KEY, name); + values.put(VALUE, Boolean.toString(val)); + // this insert is translated into an update by the underlying Sync provider + contentResolver.insert(CONTENT_URI, values); + } + + /** + * Convenience function for getting a setting value as a boolean without using the + * QueryMap for light-weight setting querying. + * @param contentResolver The ContentResolver for querying the setting. + * @param name The name of the setting to query + * @param def The default value for the setting. + * @return The value of the setting. + */ + static public boolean getBoolean(ContentResolver contentResolver, + String name, boolean def) { + Cursor cursor = contentResolver.query( + CONTENT_URI, + PROJECTION, + KEY + "=?", + new String[] { name }, + null); + try { + if (cursor != null && cursor.moveToFirst()) { + return Boolean.parseBoolean(cursor.getString(1)); + } + } finally { + if (cursor != null) cursor.close(); + } + return def; + } + + /** + * A convenience method to set whether or not the provider is synced when + * it receives a network tickle. + * + * @param contentResolver the ContentResolver to use to access the settings table + * @param providerName the provider whose behavior is being controlled + * @param sync true if the provider should be synced when tickles are received for it + */ + static public void setSyncProviderAutomatically(ContentResolver contentResolver, + String providerName, boolean sync) { + putBoolean(contentResolver, SETTING_SYNC_PROVIDER_PREFIX + providerName, sync); + } + + /** + * A convenience method to set whether or not the device should listen to tickles. + * + * @param contentResolver the ContentResolver to use to access the settings table + * @param flag true if it should listen. + */ + static public void setListenForNetworkTickles(ContentResolver contentResolver, + boolean flag) { + putBoolean(contentResolver, SETTING_LISTEN_FOR_TICKLES, flag); + } + + public static class QueryMap extends ContentQueryMap { + private ContentResolver mContentResolver; + + public QueryMap(ContentResolver contentResolver, boolean keepUpdated, + Handler handlerForUpdateNotifications) { + super(contentResolver.query(CONTENT_URI, null, null, null, null), KEY, keepUpdated, + handlerForUpdateNotifications); + mContentResolver = contentResolver; + } + + /** + * Check if the provider should be synced when a network tickle is received + * @param providerName the provider whose setting we are querying + * @return true of the provider should be synced when a network tickle is received + */ + public boolean getSyncProviderAutomatically(String providerName) { + return getBoolean(SETTING_SYNC_PROVIDER_PREFIX + providerName, true); + } + + /** + * Set whether or not the provider is synced when it receives a network tickle. + * + * @param providerName the provider whose behavior is being controlled + * @param sync true if the provider should be synced when tickles are received for it + */ + public void setSyncProviderAutomatically(String providerName, boolean sync) { + Settings.setSyncProviderAutomatically(mContentResolver, providerName, sync); + } + + /** + * Set whether or not the device should listen for tickles. + * + * @param flag true if it should listen. + */ + public void setListenForNetworkTickles(boolean flag) { + Settings.setListenForNetworkTickles(mContentResolver, flag); + } + + /** + * Check if the device should listen to tickles. + + * @return true if it should + */ + public boolean getListenForNetworkTickles() { + return getBoolean(SETTING_LISTEN_FOR_TICKLES, true); + } + + /** + * Convenience function for retrieving a single settings value + * as a boolean. + * + * @param name The name of the setting to retrieve. + * @param def Value to return if the setting is not defined. + * @return The setting's current value, or 'def' if it is not defined. + */ + private boolean getBoolean(String name, boolean def) { + ContentValues values = getValues(name); + return values != null ? values.getAsBoolean(VALUE) : def; + } + } + } +} |