visibility="public"
>
</field>
+<field name="userVisible"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="16843408"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="value"
type="int"
transient="false"
type="int"
transient="false"
volatile="false"
- value="16843408"
+ value="16843409"
static="true"
final="true"
deprecated="not deprecated"
visibility="public"
>
</field>
+<field name="SYNC_EXTRAS_INITIALIZE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value=""initialize""
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
<field name="SYNC_EXTRAS_MANUAL"
type="java.lang.String"
transient="false"
</parameter>
<parameter name="accountType" type="java.lang.String">
</parameter>
+<parameter name="userVisible" type="boolean">
+</parameter>
</constructor>
<constructor name="SyncAdapterType"
type="android.content.SyncAdapterType"
visibility="public"
>
</method>
+<method name="newKey"
+ return="android.content.SyncAdapterType"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="authority" type="java.lang.String">
+</parameter>
+<parameter name="accountType" type="java.lang.String">
+</parameter>
+</method>
<method name="writeToParcel"
return="void"
abstract="false"
visibility="public"
>
</field>
-<field name="isUserFacing"
+<field name="userVisible"
type="boolean"
transient="false"
volatile="false"
- value="true"
static="false"
final="true"
deprecated="not deprecated"
/** Kernel event log tag. Also listed in data/etc/event-log-tags. */
public static final int LOG_SYNC_DETAILS = 2743;
+ private final boolean mAutoInitialize;
/**
* Creates an {@link AbstractThreadedSyncAdapter}.
- * @param context the {@link Context} that this is running within.
+ * @param context the {@link android.content.Context} that this is running within.
+ * @param autoInitialize if true then sync requests that have
+ * {@link ContentResolver#SYNC_EXTRAS_INITIALIZE} set will be internally handled by
+ * {@link AbstractThreadedSyncAdapter} by calling
+ * {@link ContentResolver#setIsSyncable(android.accounts.Account, String, int)} with 1 if it
+ * is currently set to <0.
*/
- public AbstractThreadedSyncAdapter(Context context) {
+ public AbstractThreadedSyncAdapter(Context context, boolean autoInitialize) {
mContext = context;
mISyncAdapterImpl = new ISyncAdapterImpl();
mNumSyncStarts = new AtomicInteger(0);
mSyncThread = null;
+ mAutoInitialize = autoInitialize;
}
class ISyncAdapterImpl extends ISyncAdapter.Stub {
// check it and when we use it
synchronized (this) {
if (mSyncThread == null) {
+ if (mAutoInitialize
+ && extras != null
+ && extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) {
+ if (ContentResolver.getIsSyncable(account, authority) < 0) {
+ ContentResolver.setIsSyncable(account, authority, 1);
+ }
+ syncContextClient.onFinished(new SyncResult());
+ return;
+ }
mSyncThread = new SyncThread(
"SyncAdapterThread-" + mNumSyncStarts.incrementAndGet(),
syncContextClient, authority, account, extras);
* @deprecated instead use
* {@link #requestSync(android.accounts.Account, String, android.os.Bundle)}
*/
+ @Deprecated
public static final String SYNC_EXTRAS_ACCOUNT = "account";
public static final String SYNC_EXTRAS_EXPEDITED = "expedited";
/**
* @deprecated instead use
* {@link #SYNC_EXTRAS_MANUAL}
*/
+ @Deprecated
public static final String SYNC_EXTRAS_FORCE = "force";
public static final String SYNC_EXTRAS_MANUAL = "force";
public static final String SYNC_EXTRAS_UPLOAD = "upload";
public static final String SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS = "deletions_override";
public static final String SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS = "discard_deletions";
+ /**
+ * Set by the SyncManager to request that the SyncAdapter initialize itself for
+ * the given account/authority pair. One required initialization step is to
+ * ensure that {@link #setIsSyncable(android.accounts.Account, String, int)} has been
+ * called with a >= 0 value. When this flag is set the SyncAdapter does not need to
+ * do a full sync, though it is allowed to do so.
+ */
+ public static final String SYNC_EXTRAS_INITIALIZE = "initialize";
+
public static final String SCHEME_CONTENT = "content";
public static final String SCHEME_ANDROID_RESOURCE = "android.resource";
public static final String SCHEME_FILE = "file";
}
/**
- * Returns the status that matches the authority. If there are multiples accounts for
- * the authority, the one with the latest "lastSuccessTime" status is returned.
+ * Returns the status that matches the authority.
* @param account the account whose setting we are querying
* @param authority the provider whose behavior is being queried
* @return the SyncStatusInfo for the authority, or null if none exists
try {
SyncManager syncManager = getSyncManager();
if (syncManager != null) {
- syncManager.scheduleSync(account, authority, extras, 0 /* no delay */);
+ syncManager.scheduleSync(account, authority, extras, 0 /* no delay */,
+ false /* onlyThoseWithUnkownSyncableState */);
}
} finally {
restoreCallingIdentity(identityToken);
class Transport extends ISyncAdapter.Stub {
public void startSync(ISyncContext syncContext, String authority, Account account,
Bundle extras) throws RemoteException {
- SyncAdapter.this.startSync(new SyncContext(syncContext), account, extras);
+ SyncAdapter.this.startSync(new SyncContext(syncContext), account, authority, extras);
}
public void cancelSync(ISyncContext syncContext) throws RemoteException {
* @param syncContext the ISyncContext used to indicate the progress of the sync. When
* the sync is finished (successfully or not) ISyncContext.onFinished() must be called.
* @param account the account that should be synced
+ * @param authority the authority if the sync request
* @param extras SyncAdapter-specific parameters
*/
- public abstract void startSync(SyncContext syncContext, Account account, Bundle extras);
+ public abstract void startSync(SyncContext syncContext, Account account, String authority,
+ Bundle extras);
/**
* Cancel the most recently initiated sync. Due to race conditions, this may arrive
public class SyncAdapterType implements Parcelable {
public final String authority;
public final String accountType;
- public final boolean isUserFacing = true; // TODO: implement logic to set this
+ public final boolean userVisible;
- public SyncAdapterType(String authority, String accountType) {
+ public SyncAdapterType(String authority, String accountType, boolean userVisible) {
if (TextUtils.isEmpty(authority)) {
throw new IllegalArgumentException("the authority must not be empty: " + authority);
}
}
this.authority = authority;
this.accountType = accountType;
+ this.userVisible = userVisible;
+ }
+
+ public static SyncAdapterType newKey(String authority, String accountType) {
+ return new SyncAdapterType(authority, accountType, true);
}
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof SyncAdapterType)) return false;
final SyncAdapterType other = (SyncAdapterType)o;
+ // don't include userVisible in the equality check
return authority.equals(other.authority) && accountType.equals(other.accountType);
}
int result = 17;
result = 31 * result + authority.hashCode();
result = 31 * result + accountType.hashCode();
+ // don't include userVisible in the hash
return result;
}
public String toString() {
- return "SyncAdapterType {name=" + authority + ", type=" + accountType + "}";
+ return "SyncAdapterType {name=" + authority + ", type=" + accountType
+ + ", userVisible=" + userVisible + "}";
}
public int describeContents() {
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(authority);
dest.writeString(accountType);
+ dest.writeInt(userVisible ? 1 : 0);
}
public SyncAdapterType(Parcel source) {
- this(source.readString(), source.readString());
+ this(source.readString(), source.readString(), source.readInt() != 0);
}
public static final Creator<SyncAdapterType> CREATOR = new Creator<SyncAdapterType>() {
if (authority == null || accountType == null) {
return null;
}
- return new SyncAdapterType(authority, accountType);
+ final boolean userVisible =
+ sa.getBoolean(com.android.internal.R.styleable.SyncAdapter_userVisible, true);
+ return new SyncAdapterType(authority, accountType, userVisible);
} finally {
sa.recycle();
}
};
public void onAccountsUpdated(Account[] accounts) {
- final boolean hadAccountsAlready = mAccounts != null;
+ // remember if this was the first time this was called after an update
+ final boolean justBootedUp = mAccounts == null;
mAccounts = accounts;
// if a sync is in progress yet it is no longer in the accounts list,
mSyncStorageEngine.doDatabaseCleanup(accounts);
- if (hadAccountsAlready && accounts.length > 0) {
- // request a sync so that if the password was changed we will
- // retry any sync that failed when it was wrong
- scheduleSync(null, null, null, 0 /* no delay */);
+ if (accounts.length > 0) {
+ // If this is the first time this was called after a bootup then
+ // the accounts haven't really changed, instead they were just loaded
+ // from the AccountManager. Otherwise at least one of the accounts
+ // has a change.
+ //
+ // If there was a real account change then force a sync of all accounts.
+ // This is a bit of overkill, but at least it will end up retrying syncs
+ // that failed due to an authentication failure and thus will recover if the
+ // account change was a password update.
+ //
+ // If this was the bootup case then don't sync everything, instead only
+ // sync those that have an unknown syncable state, which will give them
+ // a chance to set their syncable state.
+ boolean onlyThoseWithUnkownSyncableState = !justBootedUp;
+ scheduleSync(null, null, null, 0 /* no delay */, onlyThoseWithUnkownSyncableState);
}
}
// perform a poll
scheduleSync(null /* sync all syncable accounts */, null /* sync all syncable providers */,
- new Bundle(), 0 /* no delay */);
+ new Bundle(), 0 /* no delay */, false /* onlyThoseWithUnkownSyncableState */);
}
private void writeSyncPollTime(long when) {
* syncs of a specific provider. Can be null. Is ignored
* if the url is null.
* @param delay how many milliseconds in the future to wait before performing this
+ * @param onlyThoseWithUnkownSyncableState
*/
public void scheduleSync(Account requestedAccount, String requestedAuthority,
- Bundle extras, long delay) {
+ Bundle extras, long delay, boolean onlyThoseWithUnkownSyncableState) {
boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE);
if (isLoggable) {
Log.v(TAG, "scheduleSync:"
for (String authority : syncableAuthorities) {
for (Account account : accounts) {
- boolean isSyncable = mSyncStorageEngine.getIsSyncable(account, authority) > 0;
- if (!isSyncable) {
+ int isSyncable = mSyncStorageEngine.getIsSyncable(account, authority);
+ if (isSyncable == 0) {
+ continue;
+ }
+ if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) {
continue;
}
- if (mSyncAdapters.getServiceInfo(new SyncAdapterType(authority, account.type))
+ if (mSyncAdapters.getServiceInfo(SyncAdapterType.newKey(authority, account.type))
!= null) {
+ // make this an initialization sync if the isSyncable state is unknown
+ Bundle extrasCopy = new Bundle(extras);
+ if (isSyncable < 0) {
+ extrasCopy.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true);
+ }
scheduleSyncOperation(
- new SyncOperation(account, source, authority, extras, delay));
+ new SyncOperation(account, source, authority, extrasCopy, delay));
}
}
}
public void scheduleLocalSync(Account account, String authority) {
final Bundle extras = new Bundle();
extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true);
- scheduleSync(account, authority, extras, LOCAL_SYNC_DELAY);
+ scheduleSync(account, authority, extras, LOCAL_SYNC_DELAY,
+ false /* onlyThoseWithUnkownSyncableState */);
}
private IPackageManager getPackageManager() {
final boolean syncAutomatically =
mSyncStorageEngine.getSyncAutomatically(op.account, op.authority)
&& mSyncStorageEngine.getMasterSyncAutomatically();
- boolean isSyncable =
- mSyncStorageEngine.getIsSyncable(op.account, op.authority) > 0;
boolean syncAllowed =
manualSync || (backgroundDataUsageAllowed && syncAutomatically);
- if (!syncAllowed || !isSyncable) {
+ int isSyncable = mSyncStorageEngine.getIsSyncable(op.account, op.authority);
+ if (isSyncable == 0) {
+ // if not syncable, don't allow
+ syncAllowed = false;
+ } else if (isSyncable < 0) {
+ // if the syncable state is unknown, only allow initialization syncs
+ syncAllowed =
+ op.extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false);
+ }
+ if (!syncAllowed) {
if (isLoggable) {
Log.v(TAG, "runStateIdle: sync off, dropping " + op);
}
}
// connect to the sync adapter
- SyncAdapterType syncAdapterType = new SyncAdapterType(op.authority,
- op.account.type);
+ SyncAdapterType syncAdapterType = SyncAdapterType.newKey(op.authority, op.account.type);
RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo =
mSyncAdapters.getServiceInfo(syncAdapterType);
if (syncAdapterInfo == null) {
this.authority = authority;
this.ident = ident;
enabled = SYNC_ENABLED_DEFAULT;
- // TODO: change the default to -1 when the syncadapters are changed to set this
- syncable = 1;
+ syncable = -1; // default to "unknown"
}
}
import android.util.Log;
import android.util.TimingLogger;
import android.accounts.Account;
+import android.accounts.AuthenticatorException;
+import android.accounts.OperationCanceledException;
+
+import java.io.IOException;
/**
* @hide
*/
public abstract boolean isReadOnly();
+ public abstract boolean getIsSyncable(Account account)
+ throws IOException, AuthenticatorException, OperationCanceledException;
+
/**
* Get diffs from the server since the last completed sync and put them
* into a temporary provider.
private class SyncThread extends Thread {
private final Account mAccount;
+ private final String mAuthority;
private final Bundle mExtras;
private final SyncContext mSyncContext;
private volatile boolean mIsCanceled = false;
private long mInitialRxBytes;
private final SyncResult mResult;
- SyncThread(SyncContext syncContext, Account account, Bundle extras) {
+ SyncThread(SyncContext syncContext, Account account, String authority, Bundle extras) {
super("SyncThread");
mAccount = account;
+ mAuthority = authority;
mExtras = extras;
mSyncContext = syncContext;
mResult = new SyncResult();
mInitialTxBytes = NetStat.getUidTxBytes(uid);
mInitialRxBytes = NetStat.getUidRxBytes(uid);
try {
- sync(mSyncContext, mAccount, mExtras);
+ sync(mSyncContext, mAccount, mAuthority, mExtras);
} catch (SQLException e) {
Log.e(TAG, "Sync failed", e);
mResult.databaseError = true;
}
}
- private void sync(SyncContext syncContext, Account account, Bundle extras) {
+ private void sync(SyncContext syncContext, Account account, String authority,
+ Bundle extras) {
mIsCanceled = false;
mProviderSyncStarted = false;
mAdapterSyncStarted = false;
String message = null;
+ // always attempt to initialize if the isSyncable state isn't set yet
+ int isSyncable = ContentResolver.getIsSyncable(account, authority);
+ if (isSyncable < 0) {
+ try {
+ isSyncable = (getIsSyncable(account)) ? 1 : 0;
+ ContentResolver.setIsSyncable(account, authority, isSyncable);
+ } catch (IOException e) {
+ ++mResult.stats.numIoExceptions;
+ } catch (AuthenticatorException e) {
+ ++mResult.stats.numParseExceptions;
+ } catch (OperationCanceledException e) {
+ // do nothing
+ }
+ }
+
+ // if this is an initialization request then our work is done here
+ if (extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false)) {
+ return;
+ }
+
+ // if we aren't syncable then get out
+ if (isSyncable <= 0) {
+ return;
+ }
+
boolean manualSync = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
try {
EventLog.writeEvent(SyncAdapter.LOG_SYNC_DETAILS, TAG, bytesSent, bytesReceived, "");
}
- public void startSync(SyncContext syncContext, Account account, Bundle extras) {
+ public void startSync(SyncContext syncContext, Account account, String authority,
+ Bundle extras) {
if (mSyncThread != null) {
syncContext.onFinished(SyncResult.ALREADY_IN_PROGRESS);
return;
}
- mSyncThread = new SyncThread(syncContext, account, extras);
+ mSyncThread = new SyncThread(syncContext, account, authority, extras);
mSyncThread.start();
}
<!-- Small inverse ProgressBar style. This is a small circular progress bar. -->
<attr name="progressBarStyleSmallInverse" format="reference" />
<!-- Large inverse ProgressBar style. This is a large circular progress bar. -->
- <attr name="progressBarStyleLargeInverse" format="reference" />
+ <attr name="progressBarStyleLargeInverse" format="reference" />
<!-- Default SeekBar style. -->
<attr name="seekBarStyle" format="reference" />
<!-- Default RatingBar style. -->
<attr name="pivotY" />
<attr name="drawable" />
</declare-styleable>
-
+
<declare-styleable name="InsetDrawable">
<attr name="visible" />
<attr name="drawable" />
results for "bo", it would not be queried again for "bob".
The default value is <code>false</code>. <i>Optional attribute.</i>. -->
<attr name="queryAfterZeroResults" format="boolean" />
-
+
<!-- If provided, this string will be used to describe the searchable item in the
searchable items settings within system search settings. <i>Optional
attribute.</i> -->
<!-- the authority of a content provider. -->
<attr name="contentAuthority" format="string"/>
<attr name="accountType"/>
+ <attr name="userVisible" format="boolean"/>
</declare-styleable>
<!-- =============================== -->
<public type="style" name="Widget.ProgressBar.Inverse" id="0x0103005b" />
<public type="style" name="Widget.ProgressBar.Large.Inverse" id="0x0103005c" />
- <public type="style" name="Widget.ProgressBar.Small.Inverse" id="0x0103005d" />
+ <public type="style" name="Widget.ProgressBar.Small.Inverse" id="0x0103005d" />
<public type="drawable" name="stat_sys_vp_phone_call" id="0x010800a7" />
<public type="drawable" name="stat_sys_vp_phone_call_on_hold" id="0x010800a8" />
-
+
<public type="anim" name="anticipate_interpolator" id="0x010a0007" />
<public type="anim" name="overshoot_interpolator" id="0x010a0008" />
<public type="anim" name="anticipate_overshoot_interpolator" id="0x010a0009" />
<public type="anim" name="bounce_interpolator" id="0x010a000a" />
<public type="anim" name="linear_interpolator" id="0x010a000b" />
-
+
<!-- ===============================================================
Resources added in Eclair.
=============================================================== -->
<public type="attr" name="accountType" />
<public type="attr" name="contentAuthority" />
+ <public type="attr" name="userVisible" />
<public type="attr" name="windowShowWallpaper" />
<public type="style" name="Theme.Wallpaper" />