OSDN Git Service

make syncadapter set whether the account is syncable
authorFred Quintana <fredq@google.com>
Mon, 17 Aug 2009 20:05:39 +0000 (13:05 -0700)
committerFred Quintana <fredq@google.com>
Tue, 18 Aug 2009 18:06:52 +0000 (11:06 -0700)
12 files changed:
api/current.xml
core/java/android/content/AbstractThreadedSyncAdapter.java
core/java/android/content/ContentResolver.java
core/java/android/content/ContentService.java
core/java/android/content/SyncAdapter.java
core/java/android/content/SyncAdapterType.java
core/java/android/content/SyncAdaptersCache.java
core/java/android/content/SyncManager.java
core/java/android/content/SyncStorageEngine.java
core/java/android/content/TempProviderSyncAdapter.java
core/res/res/values/attrs.xml
core/res/res/values/public.xml

index 808a254..000f5d9 100644 (file)
  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="&quot;initialize&quot;"
+ 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"
index f15a902..538225a 100644 (file)
@@ -44,16 +44,23 @@ public abstract class AbstractThreadedSyncAdapter {
 
     /** 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 {
@@ -66,6 +73,15 @@ public abstract class AbstractThreadedSyncAdapter {
             // 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);
index bf1a442..239b3de 100644 (file)
@@ -52,18 +52,29 @@ public abstract class ContentResolver {
      * @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";
@@ -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
index c4d8aaf..f742448 100644 (file)
@@ -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);
index 1d5ade1..88dc332 100644 (file)
@@ -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
index d3f8230..93b61ec 100644 (file)
@@ -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<SyncAdapterType> CREATOR = new Creator<SyncAdapterType>() {
index ce47d76..c27fd25 100644 (file)
@@ -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();
         }
index f50fd74..34efc51 100644 (file)
@@ -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 (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));
                 }
             }
         }
@@ -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<SyncAdapterType> syncAdapterInfo =
                     mSyncAdapters.getServiceInfo(syncAdapterType);
             if (syncAdapterInfo == null) {
index 2647962..7f78e75 100644 (file)
@@ -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"
         }
     }
     
index fb05fe7..b46c545 100644 (file)
@@ -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();
     }
 
index 01253d3..eee87e6 100644 (file)
         <!-- 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>
 
     <!-- =============================== -->
index d51b439..60b492a 100644 (file)
 
   <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" />