diff --git a/api/current.xml b/api/current.xml index 808a254aa2a82..000f5d9c6ff4c 100644 --- a/api/current.xml +++ b/api/current.xml @@ -8391,6 +8391,17 @@ visibility="public" > + + + + + + + + + + + + -= 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"; @@ -1094,8 +1105,7 @@ public abstract class ContentResolver { } /** - * 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 diff --git a/core/java/android/content/ContentService.java b/core/java/android/content/ContentService.java index c4d8aafd41780..f7424487dcfec 100644 --- a/core/java/android/content/ContentService.java +++ b/core/java/android/content/ContentService.java @@ -197,7 +197,8 @@ public final class ContentService extends IContentService.Stub { 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); diff --git a/core/java/android/content/SyncAdapter.java b/core/java/android/content/SyncAdapter.java index 1d5ade17ae904..88dc3320d3515 100644 --- a/core/java/android/content/SyncAdapter.java +++ b/core/java/android/content/SyncAdapter.java @@ -32,7 +32,7 @@ public abstract class SyncAdapter { 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 { @@ -58,9 +58,11 @@ public abstract class SyncAdapter { * @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 diff --git a/core/java/android/content/SyncAdapterType.java b/core/java/android/content/SyncAdapterType.java index d3f8230cb5b08..93b61ecc9a92d 100644 --- a/core/java/android/content/SyncAdapterType.java +++ b/core/java/android/content/SyncAdapterType.java @@ -27,9 +27,9 @@ import android.os.Parcel; 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); } @@ -38,12 +38,18 @@ public class SyncAdapterType implements Parcelable { } 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); } @@ -51,11 +57,13 @@ public class SyncAdapterType implements Parcelable { 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() { @@ -65,10 +73,11 @@ public class SyncAdapterType implements Parcelable { 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 CREATOR = new Creator() { diff --git a/core/java/android/content/SyncAdaptersCache.java b/core/java/android/content/SyncAdaptersCache.java index ce47d7672fb16..c27fd259ee12a 100644 --- a/core/java/android/content/SyncAdaptersCache.java +++ b/core/java/android/content/SyncAdaptersCache.java @@ -47,7 +47,9 @@ import android.util.AttributeSet; 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(); } diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java index f50fd74662aeb..34efc51f21d5e 100644 --- a/core/java/android/content/SyncManager.java +++ b/core/java/android/content/SyncManager.java @@ -180,7 +180,8 @@ class SyncManager implements OnAccountsUpdatedListener { }; 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, @@ -200,10 +201,22 @@ class SyncManager implements OnAccountsUpdatedListener { 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); } } @@ -406,7 +419,7 @@ class SyncManager implements OnAccountsUpdatedListener { // 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) { @@ -508,9 +521,10 @@ class SyncManager implements OnAccountsUpdatedListener { * 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:" @@ -596,14 +610,22 @@ class SyncManager implements OnAccountsUpdatedListener { 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 (mSyncAdapters.getServiceInfo(new SyncAdapterType(authority, account.type)) + if (onlyThoseWithUnkownSyncableState && isSyncable >= 0) { + continue; + } + 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)); } } } @@ -616,7 +638,8 @@ class SyncManager implements OnAccountsUpdatedListener { 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() { @@ -1588,11 +1611,18 @@ class SyncManager implements OnAccountsUpdatedListener { 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); } @@ -1636,8 +1666,7 @@ class SyncManager implements OnAccountsUpdatedListener { } // 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 syncAdapterInfo = mSyncAdapters.getServiceInfo(syncAdapterType); if (syncAdapterInfo == null) { diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java index 264796281283f..7f78e75b56999 100644 --- a/core/java/android/content/SyncStorageEngine.java +++ b/core/java/android/content/SyncStorageEngine.java @@ -162,8 +162,7 @@ public class SyncStorageEngine extends Handler { 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" } } diff --git a/core/java/android/content/TempProviderSyncAdapter.java b/core/java/android/content/TempProviderSyncAdapter.java index fb05fe7807a54..b46c5454ac2f0 100644 --- a/core/java/android/content/TempProviderSyncAdapter.java +++ b/core/java/android/content/TempProviderSyncAdapter.java @@ -13,6 +13,10 @@ import android.util.EventLog; 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 @@ -84,6 +88,9 @@ public abstract class TempProviderSyncAdapter extends SyncAdapter { */ 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. @@ -173,6 +180,7 @@ public abstract class TempProviderSyncAdapter extends SyncAdapter { 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; @@ -180,9 +188,10 @@ public abstract class TempProviderSyncAdapter extends SyncAdapter { 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(); @@ -206,7 +215,7 @@ public abstract class TempProviderSyncAdapter extends SyncAdapter { 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; @@ -220,13 +229,39 @@ public abstract class TempProviderSyncAdapter extends SyncAdapter { } } - 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 { @@ -517,13 +552,14 @@ public abstract class TempProviderSyncAdapter extends SyncAdapter { 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(); } diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 01253d330e26d..eee87e6972762 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -361,7 +361,7 @@ - + @@ -2348,7 +2348,7 @@ - + @@ -2886,7 +2886,7 @@ results for "bo", it would not be queried again for "bob". The default value is false. Optional attribute.. --> - + @@ -3359,6 +3359,7 @@ + diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index d51b4399705f6..60b492a61134a 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1130,17 +1130,17 @@ - + - + - + @@ -1148,6 +1148,7 @@ +