OSDN Git Service

change the sync framework and users to understand Account
authorFred Quintana <fredq@google.com>
Thu, 23 Apr 2009 20:36:27 +0000 (13:36 -0700)
committerFred Quintana <fredq@google.com>
Thu, 23 Apr 2009 22:37:45 +0000 (15:37 -0700)
25 files changed:
api/current.xml
core/java/android/accounts/AccountManager.java
core/java/android/accounts/AccountMonitor.java [deleted file]
core/java/android/accounts/OnAccountsUpdatedListener.java [moved from core/java/android/accounts/AccountMonitorListener.java with 85% similarity]
core/java/android/content/AbstractSyncableContentProvider.java
core/java/android/content/AbstractTableMerger.java
core/java/android/content/ContentResolver.java
core/java/android/content/ISyncAdapter.aidl
core/java/android/content/SyncAdapter.java
core/java/android/content/SyncManager.java
core/java/android/content/SyncStateContentProviderHelper.java
core/java/android/content/SyncStorageEngine.java
core/java/android/content/SyncableContentProvider.java
core/java/android/content/TempProviderSyncAdapter.java
core/java/android/provider/Calendar.java
core/java/android/provider/Contacts.java
core/java/android/provider/SubscribedFeeds.java
core/java/android/provider/Sync.java
core/java/android/provider/SyncConstValue.java
packages/SubscribedFeedsProvider/src/com/android/providers/subscribedfeeds/SubscribedFeedsIntentService.java
packages/SubscribedFeedsProvider/src/com/android/providers/subscribedfeeds/SubscribedFeedsProvider.java
preloaded-classes
test-runner/android/test/SyncBaseInstrumentation.java
tests/CoreTests/android/content/SyncStorageEngineTest.java
tests/FrameworkTest/tests/src/android/content/AbstractTableMergerTest.java

index 9125da5..caffcaf 100644 (file)
 <parameter name="handler" type="android.os.Handler">
 </parameter>
 </method>
+<method name="addOnAccountsUpdatedListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.accounts.OnAccountsUpdatedListener">
+</parameter>
+<parameter name="handler" type="android.os.Handler">
+</parameter>
+<parameter name="updateImmediately" type="boolean">
+</parameter>
+</method>
 <method name="blockingAddAccountExplicitly"
  return="boolean"
  abstract="false"
 <parameter name="handler" type="android.os.Handler">
 </parameter>
 </method>
+<method name="removeOnAccountsUpdatedListener"
+ return="void"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="listener" type="android.accounts.OnAccountsUpdatedListener">
+</parameter>
+</method>
 <method name="setAuthToken"
  return="android.accounts.Future1&lt;java.lang.Void&gt;"
  abstract="false"
 </parameter>
 </method>
 </class>
-<class name="AccountMonitor"
- extends="android.content.BroadcastReceiver"
- abstract="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<constructor name="AccountMonitor"
- type="android.accounts.AccountMonitor"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="context" type="android.content.Context">
-</parameter>
-<parameter name="listener" type="android.accounts.AccountMonitorListener">
-</parameter>
-</constructor>
-<method name="close"
- return="void"
- abstract="false"
- native="false"
- synchronized="true"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-</method>
-<method name="onReceive"
- return="void"
- abstract="false"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="context" type="android.content.Context">
-</parameter>
-<parameter name="intent" type="android.content.Intent">
-</parameter>
-</method>
-</class>
-<interface name="AccountMonitorListener"
- abstract="true"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<method name="onAccountsUpdated"
- return="void"
- abstract="true"
- native="false"
- synchronized="false"
- static="false"
- final="false"
- deprecated="not deprecated"
- visibility="public"
->
-<parameter name="currentAccounts" type="java.lang.String[]">
-</parameter>
-</method>
-</interface>
 <class name="AuthenticatorBindHelper"
  extends="java.lang.Object"
  abstract="false"
 </parameter>
 </constructor>
 </class>
+<interface name="OnAccountsUpdatedListener"
+ abstract="true"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<method name="onAccountsUpdated"
+ return="void"
+ abstract="true"
+ native="false"
+ synchronized="false"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="accounts" type="android.accounts.Account[]">
+</parameter>
+</method>
+</interface>
 <class name="OperationCanceledException"
  extends="java.lang.Exception"
  abstract="false"
  visibility="public"
 >
 </field>
+<field name="GROUP_SYNC_ACCOUNT_TYPE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;group_sync_account_type&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="GROUP_SYNC_ID"
  type="java.lang.String"
  transient="false"
  visibility="public"
 >
 </field>
+<field name="_SYNC_ACCOUNT_TYPE"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;_sync_account_type&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 </interface>
 <class name="LiveFolders"
  extends="java.lang.Object"
index 007a490..3d21101 100644 (file)
@@ -19,11 +19,15 @@ package android.accounts;
 import android.app.Activity;
 import android.content.Intent;
 import android.content.Context;
+import android.content.IntentFilter;
+import android.content.BroadcastReceiver;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.Parcelable;
+import android.util.Config;
+import android.util.Log;
 
 import java.io.IOException;
 import java.util.concurrent.Callable;
@@ -32,6 +36,10 @@ import java.util.concurrent.ExecutionException;
 import java.util.concurrent.FutureTask;
 import java.util.concurrent.TimeoutException;
 import java.util.concurrent.TimeUnit;
+import java.util.HashMap;
+import java.util.Map;
+
+import com.google.android.collect.Maps;
 
 /**
  * A class that helps with interactions with the AccountManagerService. It provides
@@ -48,6 +56,7 @@ public class AccountManager {
 
     private final Context mContext;
     private final IAccountManager mService;
+    private final Handler mMainHandler;
 
     /**
      * @hide
@@ -55,6 +64,7 @@ public class AccountManager {
     public AccountManager(Context context, IAccountManager service) {
         mContext = context;
         mService = service;
+        mMainHandler = new Handler(mContext.getMainLooper());
     }
 
     public static AccountManager get(Context context) {
@@ -454,24 +464,28 @@ public class AccountManager {
 
     private void postToHandler(Handler handler, final Future2Callback callback,
             final Future2 future) {
-        if (handler == null) {
-            handler = new Handler(mContext.getMainLooper());
-        }
-        final Handler innerHandler = handler;
-        innerHandler.post(new Runnable() {
+        handler = handler == null ? mMainHandler : handler;
+        handler.post(new Runnable() {
             public void run() {
                 callback.run(future);
             }
         });
     }
 
+    private void postToHandler(Handler handler, final OnAccountsUpdatedListener listener,
+            final Account[] accounts) {
+        handler = handler == null ? mMainHandler : handler;
+        handler.post(new Runnable() {
+            public void run() {
+                listener.onAccountsUpdated(accounts);
+            }
+        });
+    }
+
     private <V> void postToHandler(Handler handler, final Future1Callback<V> callback,
             final Future1<V> future) {
-        if (handler == null) {
-            handler = new Handler(mContext.getMainLooper());
-        }
-        final Handler innerHandler = handler;
-        innerHandler.post(new Runnable() {
+        handler = handler == null ? mMainHandler : handler;
+        handler.post(new Runnable() {
             public void run() {
                 callback.run(future);
             }
@@ -908,4 +922,143 @@ public class AccountManager {
         new GetAuthTokenByTypeAndFeaturesTask(accountType, authTokenType,  features,
                 activityForPrompting, addAccountOptions, loginOptions, callback, handler).start();
     }
+
+    private final HashMap<OnAccountsUpdatedListener, Handler> mAccountsUpdatedListeners =
+            Maps.newHashMap();
+
+    // These variable are only used from the LOGIN_ACCOUNTS_CHANGED_ACTION BroadcastReceiver
+    // and its getAccounts() callback which are both invoked only on the main thread. As a
+    // result we don't need to protect against concurrent accesses and any changes are guaranteed
+    // to be visible when used. Basically, these two variables are thread-confined.
+    private Future1<Account[]> mAccountsLookupFuture = null;
+    private boolean mAccountLookupPending = false;
+
+    /**
+     * BroadcastReceiver that listens for the LOGIN_ACCOUNTS_CHANGED_ACTION intent
+     * so that it can read the updated list of accounts and send them to the listener
+     * in mAccountsUpdatedListeners.
+     */
+    private final BroadcastReceiver mAccountsChangedBroadcastReceiver = new BroadcastReceiver() {
+        public void onReceive(final Context context, final Intent intent) {
+            if (mAccountsLookupFuture != null) {
+                // an accounts lookup is already in progress,
+                // don't bother starting another request
+                mAccountLookupPending = true;
+                return;
+            }
+            // initiate a read of the accounts
+            mAccountsLookupFuture = getAccounts(new Future1Callback<Account[]>() {
+                public void run(Future1<Account[]> future) {
+                    // clear the future so that future receives will try the lookup again
+                    mAccountsLookupFuture = null;
+
+                    // get the accounts array
+                    Account[] accounts;
+                    try {
+                        accounts = future.getResult();
+                    } catch (OperationCanceledException e) {
+                        // this should never happen, but if it does pretend we got another
+                        // accounts changed broadcast
+                        if (Config.LOGD) {
+                            Log.d(TAG, "the accounts lookup for listener notifications was "
+                                    + "canceled, try again by simulating the receipt of "
+                                    + "a LOGIN_ACCOUNTS_CHANGED_ACTION broadcast");
+                        }
+                        onReceive(context, intent);
+                        return;
+                    }
+
+                    // send the result to the listeners
+                    synchronized (mAccountsUpdatedListeners) {
+                        for (Map.Entry<OnAccountsUpdatedListener, Handler> entry :
+                                mAccountsUpdatedListeners.entrySet()) {
+                            Account[] accountsCopy = new Account[accounts.length];
+                            // send the listeners a copy to make sure that one doesn't
+                            // change what another sees
+                            System.arraycopy(accounts, 0, accountsCopy, 0, accountsCopy.length);
+                            postToHandler(entry.getValue(), entry.getKey(), accountsCopy);
+                        }
+                    }
+
+                    // If mAccountLookupPending was set when the account lookup finished it
+                    // means that we had previously ignored a LOGIN_ACCOUNTS_CHANGED_ACTION
+                    // intent because a lookup was already in progress. Now that we are done
+                    // with this lookup and notification pretend that another intent
+                    // was received by calling onReceive() directly.
+                    if (mAccountLookupPending) {
+                        mAccountLookupPending = false;
+                        onReceive(context, intent);
+                        return;
+                    }
+                }
+            }, mMainHandler);
+        }
+    };
+
+    /**
+     * Add a {@link OnAccountsUpdatedListener} to this instance of the {@link AccountManager}.
+     * The listener is guaranteed to be invoked on the thread of the Handler that is passed
+     * in or the main thread's Handler if handler is null.
+     * @param listener the listener to add
+     * @param handler the Handler whose thread will be used to invoke the listener. If null
+     * the AccountManager context's main thread will be used.
+     * @param updateImmediately if true then the listener will be invoked as a result of this
+     * call.
+     * @throws IllegalArgumentException if listener is null
+     * @throws IllegalStateException if listener was already added
+     */
+    public void addOnAccountsUpdatedListener(final OnAccountsUpdatedListener listener,
+            Handler handler, boolean updateImmediately) {
+        if (listener == null) {
+            throw new IllegalArgumentException("the listener is null");
+        }
+        synchronized (mAccountsUpdatedListeners) {
+            if (mAccountsUpdatedListeners.containsKey(listener)) {
+                throw new IllegalStateException("this listener is already added");
+            }
+            final boolean wasEmpty = mAccountsUpdatedListeners.isEmpty();
+
+            mAccountsUpdatedListeners.put(listener, handler);
+
+            if (wasEmpty) {
+                // Register a broadcast receiver to monitor account changes
+                IntentFilter intentFilter = new IntentFilter();
+                intentFilter.addAction(Constants.LOGIN_ACCOUNTS_CHANGED_ACTION);
+                mContext.registerReceiver(mAccountsChangedBroadcastReceiver, intentFilter);
+            }
+        }
+
+        if (updateImmediately) {
+            getAccounts(new Future1Callback<Account[]>() {
+                public void run(Future1<Account[]> future) {
+                    try {
+                        listener.onAccountsUpdated(future.getResult());
+                    } catch (OperationCanceledException e) {
+                        // ignore
+                    }
+                }
+            }, handler);
+        }
+    }
+
+    /**
+     * Remove an {@link OnAccountsUpdatedListener} that was previously registered with
+     * {@link #addOnAccountsUpdatedListener}.
+     * @param listener the listener to remove
+     * @throws IllegalArgumentException if listener is null
+     * @throws IllegalStateException if listener was not already added
+     */
+    public void removeOnAccountsUpdatedListener(OnAccountsUpdatedListener listener) {
+        if (listener == null) {
+            throw new IllegalArgumentException("the listener is null");
+        }
+        synchronized (mAccountsUpdatedListeners) {
+            if (mAccountsUpdatedListeners.remove(listener) == null) {
+                throw new IllegalStateException("this listener was not previously added");
+            }
+            if (mAccountsUpdatedListeners.isEmpty()) {
+                mContext.unregisterReceiver(mAccountsChangedBroadcastReceiver);
+            }
+        }
+    }
 }
diff --git a/core/java/android/accounts/AccountMonitor.java b/core/java/android/accounts/AccountMonitor.java
deleted file mode 100644 (file)
index 38032c5..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * 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.accounts;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.*;
-
-/**
- * A helper class that calls back on the provided
- * AccountMonitorListener with the set of current accounts both when
- * it gets created and whenever the set changes. It does this by
- * binding to the AccountsService and registering to receive the
- * intent broadcast when the set of accounts is changed.  The
- * connection to the accounts service is only made when it needs to
- * fetch the current list of accounts (that is, when the
- * AccountMonitor is first created, and when the intent is received).
- */
-public class AccountMonitor extends BroadcastReceiver {
-    private static final String TAG = "AccountMonitor";
-
-    private final Context mContext;
-    private final AccountMonitorListener mListener;
-    private boolean mClosed = false;
-
-    private volatile Looper mServiceLooper;
-    private volatile NotifierHandler mServiceHandler;
-
-    /**
-     * Initializes the AccountMonitor and initiates a bind to the
-     * AccountsService to get the initial account list.  For 1.0,
-     * the "list" is always a single account.
-     *
-     * @param context the context we are running in
-     * @param listener the user to notify when the account set changes
-     */
-    public AccountMonitor(Context context, AccountMonitorListener listener) {
-        if (listener == null) {
-            throw new IllegalArgumentException("listener is null");
-        }
-
-        mContext = context;
-        mListener = listener;
-
-        // Register a broadcast receiver to monitor account changes
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(Constants.LOGIN_ACCOUNTS_CHANGED_ACTION);
-        intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);  // To recover from disk-full.
-        mContext.registerReceiver(this, intentFilter);
-
-        HandlerThread thread = new HandlerThread("AccountMonitorHandlerThread");
-        thread.start();
-        mServiceLooper = thread.getLooper();
-        mServiceHandler = new NotifierHandler(mServiceLooper);
-
-        mServiceHandler.sendEmptyMessage(0);
-    }
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        notifyListener();
-    }
-
-    private Future1Callback<Account[]> mGetAccountsCallback = new Future1Callback<Account[]>() {
-        public void run(Future1<Account[]> future) {
-            try {
-                Account[] accounts = future.getResult();
-                String[] accountNames = new String[accounts.length];
-                for (int i = 0; i < accounts.length; i++) {
-                    accountNames[i] = accounts[i].mName;
-                }
-                mListener.onAccountsUpdated(accountNames);
-            } catch (OperationCanceledException e) {
-                // the request was canceled
-            }
-        }
-    };
-
-    private synchronized void notifyListener() {
-        AccountManager.get(mContext).getAccounts(mGetAccountsCallback, null /* handler */);
-    }
-
-    /**
-     * Unregisters the account receiver.  Consecutive calls to this
-     * method are harmless, but also do nothing.  Once this call is
-     * made no more notifications will occur.
-     */
-    public synchronized void close() {
-        if (!mClosed) {
-            mContext.unregisterReceiver(this);
-            mClosed = true;
-        }
-    }
-
-    private final class NotifierHandler extends Handler {
-        public NotifierHandler(Looper looper) {
-            super(looper);
-        }
-
-        public void handleMessage(Message msg) {
-            notifyListener();
-        }
-    }
-}
@@ -19,11 +19,11 @@ package android.accounts;
 /**
  * An interface that contains the callback used by the AccountMonitor
  */
-public interface AccountMonitorListener {
+public interface OnAccountsUpdatedListener {
     /**
      * This invoked when the AccountMonitor starts up and whenever the account
      * set changes.
-     * @param currentAccounts the current accounts
+     * @param accounts the current accounts
      */
-    void onAccountsUpdated(String[] currentAccounts);
+    void onAccountsUpdated(Account[] accounts);
 }
index ce6501c..edef332 100644 (file)
@@ -4,8 +4,9 @@ import android.database.sqlite.SQLiteOpenHelper;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.Cursor;
 import android.net.Uri;
-import android.accounts.AccountMonitor;
-import android.accounts.AccountMonitorListener;
+import android.accounts.OnAccountsUpdatedListener;
+import android.accounts.Account;
+import android.accounts.AccountManager;
 import android.provider.SyncConstValue;
 import android.util.Config;
 import android.util.Log;
@@ -14,10 +15,11 @@ import android.text.TextUtils;
 
 import java.util.Collections;
 import java.util.Map;
-import java.util.HashMap;
 import java.util.Vector;
 import java.util.ArrayList;
 
+import com.google.android.collect.Maps;
+
 /**
  * A specialization of the ContentProvider that centralizes functionality
  * used by ContentProviders that are syncable. It also wraps calls to the ContentProvider
@@ -32,21 +34,22 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro
     private final String mDatabaseName;
     private final int mDatabaseVersion;
     private final Uri mContentUri;
-    private AccountMonitor mAccountMonitor;
 
     /** the account set in the last call to onSyncStart() */
-    private String mSyncingAccount;
+    private Account mSyncingAccount;
 
     private SyncStateContentProviderHelper mSyncState = null;
 
-    private static final String[] sAccountProjection = new String[] {SyncConstValue._SYNC_ACCOUNT};
+    private static final String[] sAccountProjection =
+            new String[] {SyncConstValue._SYNC_ACCOUNT, SyncConstValue._SYNC_ACCOUNT_TYPE};
 
     private boolean mIsTemporary;
 
     private AbstractTableMerger mCurrentMerger = null;
     private boolean mIsMergeCancelled = false;
 
-    private static final String SYNC_ACCOUNT_WHERE_CLAUSE = SyncConstValue._SYNC_ACCOUNT + "=?";
+    private static final String SYNC_ACCOUNT_WHERE_CLAUSE =
+            SyncConstValue._SYNC_ACCOUNT + "=? AND " + SyncConstValue._SYNC_ACCOUNT_TYPE + "=?";
 
     protected boolean isTemporary() {
         return mIsTemporary;
@@ -147,21 +150,23 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro
     @Override
     public boolean onCreate() {
         if (isTemporary()) throw new IllegalStateException("onCreate() called for temp provider");
-        mOpenHelper = new AbstractSyncableContentProvider.DatabaseHelper(getContext(), mDatabaseName);
+        mOpenHelper = new AbstractSyncableContentProvider.DatabaseHelper(getContext(),
+                mDatabaseName);
         mSyncState = new SyncStateContentProviderHelper(mOpenHelper);
-
-        AccountMonitorListener listener = new AccountMonitorListener() {
-            public void onAccountsUpdated(String[] accounts) {
-                // Some providers override onAccountsChanged(); give them a database to work with.
-                mDb = mOpenHelper.getWritableDatabase();
-                onAccountsChanged(accounts);
-                TempProviderSyncAdapter syncAdapter = (TempProviderSyncAdapter)getSyncAdapter();
-                if (syncAdapter != null) {
-                    syncAdapter.onAccountsChanged(accounts);
-                }
-            }
-        };
-        mAccountMonitor = new AccountMonitor(getContext(), listener);
+        AccountManager.get(getContext()).addOnAccountsUpdatedListener(
+                new OnAccountsUpdatedListener() {
+                    public void onAccountsUpdated(Account[] accounts) {
+                        // Some providers override onAccountsChanged(); give them a database to
+                        // work with.
+                        mDb = mOpenHelper.getWritableDatabase();
+                        onAccountsChanged(accounts);
+                        TempProviderSyncAdapter syncAdapter =
+                                (TempProviderSyncAdapter)getSyncAdapter();
+                        if (syncAdapter != null) {
+                            syncAdapter.onAccountsChanged(accounts);
+                        }
+                    }
+                }, null /* handler */, true /* updateImmediately */);
 
         return true;
     }
@@ -365,8 +370,8 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro
      * @param context the sync context for the operation
      * @param account
      */
-    public void onSyncStart(SyncContext context, String account) {
-        if (TextUtils.isEmpty(account)) {
+    public void onSyncStart(SyncContext context, Account account) {
+        if (account == null) {
             throw new IllegalArgumentException("you passed in an empty account");
         }
         mSyncingAccount = account;
@@ -385,7 +390,7 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro
      * The account of the most recent call to onSyncStart()
      * @return the account
      */
-    public String getSyncingAccount() {
+    public Account getSyncingAccount() {
         return mSyncingAccount;
     }
 
@@ -496,12 +501,11 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro
      * Make sure that there are no entries for accounts that no longer exist
      * @param accountsArray the array of currently-existing accounts
      */
-    protected void onAccountsChanged(String[] accountsArray) {
-        Map<String, Boolean> accounts = new HashMap<String, Boolean>();
-        for (String account : accountsArray) {
+    protected void onAccountsChanged(Account[] accountsArray) {
+        Map<Account, Boolean> accounts = Maps.newHashMap();
+        for (Account account : accountsArray) {
             accounts.put(account, false);
         }
-        accounts.put(SyncConstValue.NON_SYNCABLE_ACCOUNT, false);
 
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         Map<String, String> tableMap = db.getSyncedTables();
@@ -513,8 +517,7 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro
         try {
             mSyncState.onAccountsChanged(accountsArray);
             for (String table : tables) {
-                deleteRowsForRemovedAccounts(accounts, table,
-                        SyncConstValue._SYNC_ACCOUNT);
+                deleteRowsForRemovedAccounts(accounts, table);
             }
             db.setTransactionSuccessful();
         } finally {
@@ -529,23 +532,23 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro
      *
      * @param accounts a map of existing accounts
      * @param table the table to delete from
-     * @param accountColumnName the name of the column that is expected
-     * to hold the account.
      */
-    protected void deleteRowsForRemovedAccounts(Map<String, Boolean> accounts,
-            String table, String accountColumnName) {
+    protected void deleteRowsForRemovedAccounts(Map<Account, Boolean> accounts, String table) {
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         Cursor c = db.query(table, sAccountProjection, null, null,
-                accountColumnName, null, null);
+                "_sync_account, _sync_account_type", null, null);
         try {
             while (c.moveToNext()) {
-                String account = c.getString(0);
-                if (TextUtils.isEmpty(account)) {
+                String accountName = c.getString(0);
+                String accountType = c.getString(1);
+                if (TextUtils.isEmpty(accountName)) {
                     continue;
                 }
+                Account account = new Account(accountName, accountType);
                 if (!accounts.containsKey(account)) {
                     int numDeleted;
-                    numDeleted = db.delete(table, accountColumnName + "=?", new String[]{account});
+                    numDeleted = db.delete(table, "_sync_account=? AND _sync_account_type=?",
+                            new String[]{account.mName, account.mType});
                     if (Config.LOGV) {
                         Log.v(TAG, "deleted " + numDeleted
                                 + " records from table " + table
@@ -562,7 +565,7 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro
      * Called when the sync system determines that this provider should no longer
      * contain records for the specified account.
      */
-    public void wipeAccount(String account) {
+    public void wipeAccount(Account account) {
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         Map<String, String> tableMap = db.getSyncedTables();
         ArrayList<String> tables = new ArrayList<String>();
@@ -577,7 +580,8 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro
 
             // remove the data in the synced tables
             for (String table : tables) {
-                db.delete(table, SYNC_ACCOUNT_WHERE_CLAUSE, new String[]{account});
+                db.delete(table, SYNC_ACCOUNT_WHERE_CLAUSE,
+                        new String[]{account.mName, account.mType});
             }
             db.setTransactionSuccessful();
         } finally {
@@ -588,14 +592,14 @@ public abstract class AbstractSyncableContentProvider extends SyncableContentPro
     /**
      * Retrieves the SyncData bytes for the given account. The byte array returned may be null.
      */
-    public byte[] readSyncDataBytes(String account) {
+    public byte[] readSyncDataBytes(Account account) {
         return mSyncState.readSyncDataBytes(mOpenHelper.getReadableDatabase(), account);
     }
 
     /**
      * Sets the SyncData bytes for the given account. The byte array may be null.
      */
-    public void writeSyncDataBytes(String account, byte[] data) {
+    public void writeSyncDataBytes(Account account, byte[] data) {
         mSyncState.writeSyncDataBytes(mOpenHelper.getWritableDatabase(), account, data);
     }
 }
index 5588513..8c9955a 100644 (file)
@@ -25,6 +25,7 @@ import android.provider.BaseColumns;
 import static android.provider.SyncConstValue.*;
 import android.text.TextUtils;
 import android.util.Log;
+import android.accounts.Account;
 
 /**
  * @hide
@@ -55,14 +56,16 @@ public abstract class AbstractTableMerger
 
     private volatile boolean mIsMergeCancelled;
 
-    private static final String SELECT_MARKED = _SYNC_MARK + "> 0 and " + _SYNC_ACCOUNT + "=?";
+    private static final String SELECT_MARKED = _SYNC_MARK + "> 0 and "
+            + _SYNC_ACCOUNT + "=? and " + _SYNC_ACCOUNT_TYPE + "=?";
 
     private static final String SELECT_BY_SYNC_ID_AND_ACCOUNT =
-            _SYNC_ID +"=? and " + _SYNC_ACCOUNT + "=?";
+            _SYNC_ID +"=? and " + _SYNC_ACCOUNT + "=? and " + _SYNC_ACCOUNT_TYPE + "=?";
     private static final String SELECT_BY_ID = BaseColumns._ID +"=?";
 
     private static final String SELECT_UNSYNCED = ""
-            + _SYNC_DIRTY + " > 0 and (" + _SYNC_ACCOUNT + "=? or " + _SYNC_ACCOUNT + " is null)";
+            + _SYNC_DIRTY + " > 0 and ((" + _SYNC_ACCOUNT + "=? AND " + _SYNC_ACCOUNT_TYPE + "=?) "
+            + "or " + _SYNC_ACCOUNT + " is null)";
 
     public AbstractTableMerger(SQLiteDatabase database,
             String table, Uri tableURL, String deletedTable,
@@ -132,7 +135,7 @@ public abstract class AbstractTableMerger
      * construct a temporary instance to hold them.
      */
     public void merge(final SyncContext context,
-            final String account,
+            final Account account,
             final SyncableContentProvider serverDiffs,
             TempProviderSyncResult result,
             SyncResult syncResult, SyncableContentProvider temporaryInstanceFactory) {
@@ -155,7 +158,7 @@ public abstract class AbstractTableMerger
      * @hide this is public for testing purposes only
      */
     public void mergeServerDiffs(SyncContext context,
-            String account, SyncableContentProvider serverDiffs, SyncResult syncResult) {
+            Account account, SyncableContentProvider serverDiffs, SyncResult syncResult) {
         boolean diffsArePartial = serverDiffs.getContainsDiffs();
         // mark the current rows so that we can distinguish these from new
         // inserts that occur during the merge
@@ -169,7 +172,7 @@ public abstract class AbstractTableMerger
         Cursor diffsCursor = null;
         try {
             // load the local database entries, so we can merge them with the server
-            final String[] accountSelectionArgs = new String[]{account};
+            final String[] accountSelectionArgs = new String[]{account.mName, account.mType};
             localCursor = mDb.query(mTable, syncDirtyProjection,
                     SELECT_MARKED, accountSelectionArgs, null, null,
                     mTable + "." + _SYNC_ID);
@@ -462,7 +465,7 @@ public abstract class AbstractTableMerger
         }
     }
 
-    private void fullyDeleteMatchingRows(Cursor diffsCursor, String account,
+    private void fullyDeleteMatchingRows(Cursor diffsCursor, Account account,
             SyncResult syncResult) {
         int serverSyncIdColumn = diffsCursor.getColumnIndexOrThrow(_SYNC_ID);
         final boolean deleteBySyncId = !diffsCursor.isNull(serverSyncIdColumn);
@@ -472,7 +475,8 @@ public abstract class AbstractTableMerger
         Cursor c = null;
         try {
             if (deleteBySyncId) {
-                selectionArgs = new String[]{diffsCursor.getString(serverSyncIdColumn), account};
+                selectionArgs = new String[]{diffsCursor.getString(serverSyncIdColumn),
+                        account.mName, account.mType};
                 c = mDb.query(mTable, new String[]{BaseColumns._ID}, SELECT_BY_SYNC_ID_AND_ACCOUNT,
                         selectionArgs, null, null, null);
             } else {
@@ -505,21 +509,21 @@ public abstract class AbstractTableMerger
      * Finds local changes, placing the results in the given result object.
      * @param temporaryInstanceFactory As an optimization for the case
      * where there are no client-side diffs, mergeResult may initially
-     * have no {@link android.content.TempProviderSyncResult#tempContentProvider}.  If this is
+     * have no {@link TempProviderSyncResult#tempContentProvider}.  If this is
      * the first in the sequence of AbstractTableMergers to find
      * client-side diffs, it will use the given ContentProvider to
      * create a temporary instance and store its {@link
-     * ContentProvider} in the mergeResult.
+     * android.content.ContentProvider} in the mergeResult.
      * @param account
      * @param syncResult
      */
     private void findLocalChanges(TempProviderSyncResult mergeResult,
-            SyncableContentProvider temporaryInstanceFactory, String account,
+            SyncableContentProvider temporaryInstanceFactory, Account account,
             SyncResult syncResult) {
         SyncableContentProvider clientDiffs = mergeResult.tempContentProvider;
         if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "generating client updates");
 
-        final String[] accountSelectionArgs = new String[]{account};
+        final String[] accountSelectionArgs = new String[]{account.mName, account.mType};
 
         // Generate the client updates and insertions
         // Create a cursor for dirty records
@@ -553,7 +557,8 @@ public abstract class AbstractTableMerger
         if (mDeletedTable != null) {
             Cursor deletedCursor = mDb.query(mDeletedTable,
                     syncIdAndVersionProjection,
-                    _SYNC_ACCOUNT + "=? AND " + _SYNC_ID + " IS NOT NULL", accountSelectionArgs,
+                    _SYNC_ACCOUNT + "=? AND " + _SYNC_ACCOUNT_TYPE + "=? AND "
+                            + _SYNC_ID + " IS NOT NULL", accountSelectionArgs,
                     null, null, mDeletedTable + "." + _SYNC_ID);
             try {
                 numDeletedEntries = deletedCursor.getCount();
index 0a71d57..6577236 100644 (file)
@@ -28,6 +28,7 @@ import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.text.TextUtils;
+import android.accounts.Account;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -691,6 +692,7 @@ public abstract class ContentResolver {
      * <li>Float</li>
      * <li>Double</li>
      * <li>String</li>
+     * <li>Account</li>
      * <li>null</li>
      * </ul>
      * @param extras the Bundle to check
@@ -706,6 +708,7 @@ public abstract class ContentResolver {
                 if (value instanceof Float) continue;
                 if (value instanceof Double) continue;
                 if (value instanceof String) continue;
+                if (value instanceof Account) continue;
                 throw new IllegalArgumentException("unexpected value type: "
                         + value.getClass().getName());
             }
index 671188c..d228605 100644 (file)
@@ -16,6 +16,7 @@
 
 package android.content;
 
+import android.accounts.Account;
 import android.os.Bundle;
 import android.content.ISyncContext;
 
@@ -33,7 +34,7 @@ oneway interface ISyncAdapter {
      * @param account the account that should be synced
      * @param extras SyncAdapter-specific parameters
      */
-    void startSync(ISyncContext syncContext, String account, in Bundle extras);
+    void startSync(ISyncContext syncContext, in Account account, in Bundle extras);
 
     /**
      * Cancel the most recently initiated sync. Due to race conditions, this may arrive
index 7826e50..3e91626 100644 (file)
@@ -18,6 +18,7 @@ package android.content;
 
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.accounts.Account;
 
 /**
  * @hide
@@ -29,7 +30,7 @@ public abstract class SyncAdapter {
     public static final int LOG_SYNC_DETAILS = 2743;
 
     class Transport extends ISyncAdapter.Stub {
-        public void startSync(ISyncContext syncContext, String account,
+        public void startSync(ISyncContext syncContext, Account account,
                 Bundle extras) throws RemoteException {
             SyncAdapter.this.startSync(new SyncContext(syncContext), account, extras);
         }
@@ -59,7 +60,7 @@ public abstract class SyncAdapter {
      * @param account the account that should be synced
      * @param extras SyncAdapter-specific parameters
      */
-    public abstract void startSync(SyncContext syncContext, String account, Bundle extras);
+    public abstract void startSync(SyncContext syncContext, Account account, Bundle extras);
 
     /**
      * Cancel the most recently initiated sync. Due to race conditions, this may arrive
index 01b07eb..4474c62 100644 (file)
@@ -21,8 +21,9 @@ import com.google.android.collect.Maps;
 import com.android.internal.R;
 import com.android.internal.util.ArrayUtils;
 
-import android.accounts.AccountMonitor;
-import android.accounts.AccountMonitorListener;
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.OnAccountsUpdatedListener;
 import android.app.AlarmManager;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -50,8 +51,6 @@ import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
-import android.preference.Preference;
-import android.preference.PreferenceGroup;
 import android.provider.Sync;
 import android.provider.Settings;
 import android.provider.Sync.History;
@@ -84,7 +83,7 @@ import java.util.Observable;
 /**
  * @hide
  */
-class SyncManager {
+class SyncManager implements OnAccountsUpdatedListener {
     private static final String TAG = "SyncManager";
 
     // used during dumping of the Sync history
@@ -130,9 +129,7 @@ class SyncManager {
     private String mStatusText = "";
     private long mHeartbeatTime = 0;
 
-    private AccountMonitor mAccountMonitor;
-
-    private volatile String[] mAccounts = null;
+    private volatile Account[] mAccounts = null;
 
     volatile private PowerManager.WakeLock mSyncWakeLock;
     volatile private PowerManager.WakeLock mHandleAlarmWakeLock;
@@ -184,43 +181,39 @@ class SyncManager {
     private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() {
         public void onReceive(Context context, Intent intent) {
             if (!mFactoryTest) {
-                AccountMonitorListener listener = new AccountMonitorListener() {
-                    public void onAccountsUpdated(String[] accounts) {
-                        final boolean hadAccountsAlready = mAccounts != null;
-                        // copy the accounts into a new array and change mAccounts to point to it
-                        String[] newAccounts = new String[accounts.length];
-                        System.arraycopy(accounts, 0, newAccounts, 0, accounts.length);
-                        mAccounts = newAccounts;
-
-                        // if a sync is in progress yet it is no longer in the accounts list,
-                        // cancel it
-                        ActiveSyncContext activeSyncContext = mActiveSyncContext;
-                        if (activeSyncContext != null) {
-                            if (!ArrayUtils.contains(newAccounts,
-                                    activeSyncContext.mSyncOperation.account)) {
-                                Log.d(TAG, "canceling sync since the account has been removed");
-                                sendSyncFinishedOrCanceledMessage(activeSyncContext,
-                                        null /* no result since this is a cancel */);
-                            }
-                        }
-
-                        // we must do this since we don't bother scheduling alarms when
-                        // the accounts are not set yet
-                        sendCheckAlarmsMessage();
+                AccountManager.get(mContext).addOnAccountsUpdatedListener(SyncManager.this,
+                        mSyncHandler, true /* updateImmediately */);
+            }
+        }
+    };
 
-                        mSyncStorageEngine.doDatabaseCleanup(accounts);
+    public void onAccountsUpdated(Account[] accounts) {
+        final boolean hadAccountsAlready = mAccounts != null;
+        mAccounts = accounts;
 
-                        if (hadAccountsAlready && mAccounts.length > 0) {
-                            // request a sync so that if the password was changed we will
-                            // retry any sync that failed when it was wrong
-                            startSync(null /* all providers */, null /* no extras */);
-                        }
-                    }
-                };
-                mAccountMonitor = new AccountMonitor(context, listener);
+        // if a sync is in progress yet it is no longer in the accounts list,
+        // cancel it
+        ActiveSyncContext activeSyncContext = mActiveSyncContext;
+        if (activeSyncContext != null) {
+            if (!ArrayUtils.contains(accounts, activeSyncContext.mSyncOperation.account)) {
+                Log.d(TAG, "canceling sync since the account has been removed");
+                sendSyncFinishedOrCanceledMessage(activeSyncContext,
+                        null /* no result since this is a cancel */);
             }
         }
-    };
+
+        // we must do this since we don't bother scheduling alarms when
+        // the accounts are not set yet
+        sendCheckAlarmsMessage();
+
+        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
+            startSync(null /* all providers */, null /* no extras */);
+        }
+    }
 
     private BroadcastReceiver mConnectivityIntentReceiver =
             new BroadcastReceiver() {
@@ -486,7 +479,7 @@ class SyncManager {
         }
     }
 
-    public String getSyncingAccount() {
+    public Account getSyncingAccount() {
         ActiveSyncContext activeSyncContext = mActiveSyncContext;
         return (activeSyncContext != null) ? activeSyncContext.mSyncOperation.account : null;
     }
@@ -557,10 +550,10 @@ class SyncManager {
             delay = -1; // this means schedule at the front of the queue
         }
 
-        String[] accounts;
-        String accountFromExtras = extras.getString(ContentResolver.SYNC_EXTRAS_ACCOUNT);
-        if (!TextUtils.isEmpty(accountFromExtras)) {
-            accounts = new String[]{accountFromExtras};
+        Account[] accounts;
+        Account accountFromExtras = extras.getParcelable(ContentResolver.SYNC_EXTRAS_ACCOUNT);
+        if (accountFromExtras != null) {
+            accounts = new Account[]{accountFromExtras};
         } else {
             // if the accounts aren't configured yet then we can't support an account-less
             // sync request
@@ -605,7 +598,7 @@ class SyncManager {
         for (int i = 0; i < numProviders; i++) {
             if (!providers.get(i).isSyncable) continue;
             final String name = names.get(i);
-            for (String account : accounts) {
+            for (Account account : accounts) {
                 scheduleSyncOperation(new SyncOperation(account, source, name, extras, delay));
                 // TODO: remove this when Calendar supports multiple accounts. Until then
                 // pretend that only the first account exists when syncing calendar.
@@ -881,7 +874,7 @@ class SyncManager {
      * Value type that represents a sync operation.
      */
     static class SyncOperation implements Comparable {
-        final String account;
+        final Account account;
         int syncSource;
         String authority;
         Bundle extras;
@@ -890,7 +883,7 @@ class SyncManager {
         long delay;
         Long rowId = null;
 
-        SyncOperation(String account, int source, String authority, Bundle extras, long delay) {
+        SyncOperation(Account account, int source, String authority, Bundle extras, long delay) {
             this.account = account;
             this.syncSource = source;
             this.authority = authority;
@@ -1024,7 +1017,7 @@ class SyncManager {
         sb.append("data connected: ").append(mDataConnectionIsConnected).append("\n");
         sb.append("memory low: ").append(mStorageIsLow).append("\n");
 
-        final String[] accounts = mAccounts;
+        final Account[] accounts = mAccounts;
         sb.append("accounts: ");
         if (accounts != null) {
             sb.append(accounts.length);
@@ -1095,17 +1088,18 @@ class SyncManager {
             c.close();
         }
 
-        String currentAccount = null;
+        Account currentAccount = null;
         c = mSyncStorageEngine.query(Sync.Status.CONTENT_URI,
-                STATUS_PROJECTION, null, null, "account, authority");
+                STATUS_PROJECTION, null, null, "account_type, account, authority");
         sb.append("\nSync history by account and authority\n");
         try {
             while (c.moveToNext()) {
-                if (!TextUtils.equals(currentAccount, c.getString(0))) {
+                final Account account = new Account(c.getString(0), c.getString(13));
+                if (!account.equals(currentAccount)) {
                     if (currentAccount != null) {
                         dumpSyncHistoryFooter(sb);
                     }
-                    currentAccount = c.getString(0);
+                    currentAccount = account;
                     dumpSyncHistoryHeader(sb, currentAccount);
                 }
 
@@ -1117,8 +1111,8 @@ class SyncManager {
         }
     }
 
-    private void dumpSyncHistoryHeader(StringBuilder sb, String account) {
-        sb.append(" Account: ").append(account).append("\n");
+    private void dumpSyncHistoryHeader(StringBuilder sb, Account account) {
+        sb.append(" ").append(account).append("\n");
         sb.append("  ___________________________________________________________________________________________________________________________\n");
         sb.append(" |                 |             num times synced           |   total  |         last success          |                     |\n");
         sb.append(" | authority       | local |  poll | server |  user | total | duration |  source |               time  |   result if failing |\n");
@@ -1137,7 +1131,8 @@ class SyncManager {
             Sync.Status.LAST_SUCCESS_TIME, // 9
             Sync.Status.LAST_FAILURE_SOURCE, // 10
             Sync.Status.LAST_FAILURE_TIME, // 11
-            Sync.Status.LAST_FAILURE_MESG // 12
+            Sync.Status.LAST_FAILURE_MESG, // 12
+            Sync.Status.ACCOUNT_TYPE, // 13
     };
 
     private void dumpSyncHistoryRow(StringBuilder sb, Cursor c) {
@@ -1305,7 +1300,7 @@ class SyncManager {
          */
         class SyncNotificationInfo {
             // only valid if isActive is true
-            public String account;
+            public Account account;
 
             // only valid if isActive is true
             public String authority;
@@ -1460,7 +1455,7 @@ class SyncManager {
 
             // If the accounts aren't known yet then we aren't ready to run. We will be kicked
             // when the account lookup request does complete.
-            String[] accounts = mAccounts;
+            Account[] accounts = mAccounts;
             if (accounts == null) {
                 if (isLoggable) {
                     Log.v(TAG, "runStateIdle: accounts not known, skipping");
@@ -1857,7 +1852,7 @@ class SyncManager {
             mContext.sendBroadcast(syncStateIntent);
         }
 
-        private void installHandleTooManyDeletesNotification(String account, String authority,
+        private void installHandleTooManyDeletesNotification(Account account, String authority,
                 long numDeletes) {
             if (mNotificationMgr == null) return;
             Intent clickIntent = new Intent();
@@ -1937,14 +1932,16 @@ class SyncManager {
                 "_id",
                 "authority",
                 "account",
+                "account_type",
                 "extras",
-                "source"
+                "source",
         };
         private static final int COLUMN_ID = 0;
         private static final int COLUMN_AUTHORITY = 1;
         private static final int COLUMN_ACCOUNT = 2;
-        private static final int COLUMN_EXTRAS = 3;
-        private static final int COLUMN_SOURCE = 4;
+        private static final int COLUMN_ACCOUNT_TYPE = 3;
+        private static final int COLUMN_EXTRAS = 4;
+        private static final int COLUMN_SOURCE = 5;
 
         private static final boolean DEBUG_CHECK_DATA_CONSISTENCY = false;
 
@@ -2026,7 +2023,8 @@ class SyncManager {
                     parcel.recycle();
                 }
                 ContentValues values = new ContentValues();
-                values.put("account", operation.account);
+                values.put("account", operation.account.mName);
+                values.put("account_type", operation.account.mType);
                 values.put("authority", operation.authority);
                 values.put("source", operation.syncSource);
                 values.put("extras", extrasData);
@@ -2084,7 +2082,7 @@ class SyncManager {
             if (DEBUG_CHECK_DATA_CONSISTENCY) debugCheckDataStructures(true /* check the DB */);
         }
 
-        public void clear(String account, String authority) {
+        public void clear(Account account, String authority) {
             Iterator<Map.Entry<String, SyncOperation>> entries = mOpsByKey.entrySet().iterator();
             while (entries.hasNext()) {
                 Map.Entry<String, SyncOperation> entry = entries.next();
@@ -2175,7 +2173,8 @@ class SyncManager {
             }
 
             SyncOperation syncOperation = new SyncOperation(
-                    cursor.getString(COLUMN_ACCOUNT),
+                    new Account(cursor.getString(COLUMN_ACCOUNT),
+                            cursor.getString(COLUMN_ACCOUNT_TYPE)),
                     cursor.getInt(COLUMN_SOURCE),
                     cursor.getString(COLUMN_AUTHORITY),
                     extras,
index f503e6f..dc728ec 100644 (file)
@@ -23,6 +23,7 @@ import android.database.DatabaseUtils;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
 import android.net.Uri;
+import android.accounts.Account;
 
 /**
  * Extends the schema of a ContentProvider to include the _sync_state table
@@ -43,14 +44,15 @@ public class SyncStateContentProviderHelper {
     private static final Uri CONTENT_URI =
             Uri.parse("content://" + SYNC_STATE_AUTHORITY + "/state");
 
-    private static final String ACCOUNT_WHERE = "_sync_account = ?";
+    private static final String ACCOUNT_WHERE = "_sync_account = ? AND _sync_account_type = ?";
 
     private final Provider mInternalProviderInterface;
 
     private static final String SYNC_STATE_TABLE = "_sync_state";
-    private static long DB_VERSION = 2;
+    private static long DB_VERSION = 3;
 
-    private static final String[] ACCOUNT_PROJECTION = new String[]{"_sync_account"};
+    private static final String[] ACCOUNT_PROJECTION =
+            new String[]{"_sync_account", "_sync_account_type"};
 
     static {
         sURIMatcher.addURI(SYNC_STATE_AUTHORITY, "state", STATE);
@@ -70,8 +72,9 @@ public class SyncStateContentProviderHelper {
         db.execSQL("CREATE TABLE _sync_state (" +
                    "_id INTEGER PRIMARY KEY," +
                    "_sync_account TEXT," +
+                   "_sync_account_type TEXT," +
                    "data TEXT," +
-                   "UNIQUE(_sync_account)" +
+                   "UNIQUE(_sync_account, _sync_account_type)" +
                    ");");
 
         db.execSQL("DROP TABLE IF EXISTS _sync_state_metadata");
@@ -168,15 +171,17 @@ public class SyncStateContentProviderHelper {
      * @param account the account of the row that should be copied over.
      */
     public void copySyncState(SQLiteDatabase dbSrc, SQLiteDatabase dbDest,
-            String account) {
-        final String[] whereArgs = new String[]{account};
-        Cursor c = dbSrc.query(SYNC_STATE_TABLE, new String[]{"_sync_account", "data"},
+            Account account) {
+        final String[] whereArgs = new String[]{account.mName, account.mType};
+        Cursor c = dbSrc.query(SYNC_STATE_TABLE,
+                new String[]{"_sync_account", "_sync_account_type", "data"},
                 ACCOUNT_WHERE, whereArgs, null, null, null);
         try {
             if (c.moveToNext()) {
                 ContentValues values = new ContentValues();
                 values.put("_sync_account", c.getString(0));
-                values.put("data", c.getBlob(1));
+                values.put("_sync_account_type", c.getString(1));
+                values.put("data", c.getBlob(2));
                 dbDest.replace(SYNC_STATE_TABLE, "_sync_account", values);
             }
         } finally {
@@ -184,14 +189,17 @@ public class SyncStateContentProviderHelper {
         }
     }
 
-    public void onAccountsChanged(String[] accounts) {
+    public void onAccountsChanged(Account[] accounts) {
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         Cursor c = db.query(SYNC_STATE_TABLE, ACCOUNT_PROJECTION, null, null, null, null, null);
         try {
             while (c.moveToNext()) {
-                final String account = c.getString(0);
+                final String accountName = c.getString(0);
+                final String accountType = c.getString(1);
+                Account account = new Account(accountName, accountType);
                 if (!ArrayUtils.contains(accounts, account)) {
-                    db.delete(SYNC_STATE_TABLE, ACCOUNT_WHERE, new String[]{account});
+                    db.delete(SYNC_STATE_TABLE, ACCOUNT_WHERE,
+                            new String[]{accountName, accountType});
                 }
             }
         } finally {
@@ -199,9 +207,9 @@ public class SyncStateContentProviderHelper {
         }
     }
 
-    public void discardSyncData(SQLiteDatabase db, String account) {
+    public void discardSyncData(SQLiteDatabase db, Account account) {
         if (account != null) {
-            db.delete(SYNC_STATE_TABLE, ACCOUNT_WHERE, new String[]{account});
+            db.delete(SYNC_STATE_TABLE, ACCOUNT_WHERE, new String[]{account.mName, account.mType});
         } else {
             db.delete(SYNC_STATE_TABLE, null, null);
         }
@@ -210,9 +218,9 @@ public class SyncStateContentProviderHelper {
     /**
      * Retrieves the SyncData bytes for the given account. The byte array returned may be null.
      */
-    public byte[] readSyncDataBytes(SQLiteDatabase db, String account) {
+    public byte[] readSyncDataBytes(SQLiteDatabase db, Account account) {
         Cursor c = db.query(SYNC_STATE_TABLE, null, ACCOUNT_WHERE,
-                new String[]{account}, null, null, null);
+                new String[]{account.mName, account.mType}, null, null, null);
         try {
             if (c.moveToFirst()) {
                 return c.getBlob(c.getColumnIndexOrThrow("data"));
@@ -226,9 +234,10 @@ public class SyncStateContentProviderHelper {
     /**
      * Sets the SyncData bytes for the given account. The bytes array may be null.
      */
-    public void writeSyncDataBytes(SQLiteDatabase db, String account, byte[] data) {
+    public void writeSyncDataBytes(SQLiteDatabase db, Account account, byte[] data) {
         ContentValues values = new ContentValues();
         values.put("data", data);
-        db.update(SYNC_STATE_TABLE, values, ACCOUNT_WHERE, new String[]{account});
+        db.update(SYNC_STATE_TABLE, values, ACCOUNT_WHERE,
+                new String[]{account.mName, account.mType});
     }
 }
index 282f6e7..2ad44d2 100644 (file)
@@ -1,6 +1,7 @@
 package android.content;
 
 import android.Manifest;
+import android.accounts.Account;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
 import android.database.sqlite.SQLiteDatabase;
@@ -16,6 +17,8 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 
+import com.google.android.collect.Sets;
+
 /**
  * ContentProvider that tracks the sync data and overall sync
  * history on the device.
@@ -26,7 +29,7 @@ public class SyncStorageEngine {
     private static final String TAG = "SyncManager";
 
     private static final String DATABASE_NAME = "syncmanager.db";
-    private static final int DATABASE_VERSION = 10;
+    private static final int DATABASE_VERSION = 11;
 
     private static final int STATS = 1;
     private static final int STATS_ID = 2;
@@ -63,17 +66,20 @@ public class SyncStorageEngine {
         PENDING_PROJECTION_MAP = map = new HashMap<String,String>();
         map.put(Sync.History._ID, Sync.History._ID);
         map.put(Sync.History.ACCOUNT, Sync.History.ACCOUNT);
+        map.put(Sync.History.ACCOUNT_TYPE, Sync.History.ACCOUNT_TYPE);
         map.put(Sync.History.AUTHORITY, Sync.History.AUTHORITY);
 
         ACTIVE_PROJECTION_MAP = map = new HashMap<String,String>();
         map.put(Sync.History._ID, Sync.History._ID);
         map.put(Sync.History.ACCOUNT, Sync.History.ACCOUNT);
+        map.put(Sync.History.ACCOUNT_TYPE, Sync.History.ACCOUNT_TYPE);
         map.put(Sync.History.AUTHORITY, Sync.History.AUTHORITY);
         map.put("startTime", "startTime");
 
         HISTORY_PROJECTION_MAP = map = new HashMap<String,String>();
         map.put(Sync.History._ID, "history._id as _id");
         map.put(Sync.History.ACCOUNT, "stats.account as account");
+        map.put(Sync.History.ACCOUNT_TYPE, "stats.account_type as account_type");
         map.put(Sync.History.AUTHORITY, "stats.authority as authority");
         map.put(Sync.History.EVENT, Sync.History.EVENT);
         map.put(Sync.History.EVENT_TIME, Sync.History.EVENT_TIME);
@@ -86,6 +92,7 @@ public class SyncStorageEngine {
         STATUS_PROJECTION_MAP = map = new HashMap<String,String>();
         map.put(Sync.Status._ID, "status._id as _id");
         map.put(Sync.Status.ACCOUNT, "stats.account as account");
+        map.put(Sync.Status.ACCOUNT_TYPE, "stats.account_type as account_type");
         map.put(Sync.Status.AUTHORITY, "stats.authority as authority");
         map.put(Sync.Status.TOTAL_ELAPSED_TIME, Sync.Status.TOTAL_ELAPSED_TIME);
         map.put(Sync.Status.NUM_SYNCS, Sync.Status.NUM_SYNCS);
@@ -102,7 +109,7 @@ public class SyncStorageEngine {
     }
 
     private static final String[] STATS_ACCOUNT_PROJECTION =
-            new String[] { Sync.Stats.ACCOUNT };
+            new String[] { Sync.Stats.ACCOUNT, Sync.Stats.ACCOUNT_TYPE };
 
     private static final int MAX_HISTORY_EVENTS_TO_KEEP = 5000;
 
@@ -151,6 +158,7 @@ public class SyncStorageEngine {
                     + "_id INTEGER PRIMARY KEY,"
                     + "authority TEXT NOT NULL,"
                     + "account TEXT NOT NULL,"
+                    + "account_type TEXT NOT NULL,"
                     + "extras BLOB NOT NULL,"
                     + "source INTEGER NOT NULL"
                     + ");");
@@ -158,6 +166,7 @@ public class SyncStorageEngine {
             db.execSQL("CREATE TABLE stats (" +
                        "_id INTEGER PRIMARY KEY," +
                        "account TEXT, " +
+                       "account_type TEXT, " +
                        "authority TEXT, " +
                        "syncdata TEXT, " +
                        "UNIQUE (account, authority)" +
@@ -195,6 +204,7 @@ public class SyncStorageEngine {
                     + "_id INTEGER PRIMARY KEY,"
                     + "authority TEXT,"
                     + "account TEXT,"
+                    + "account_type TEXT,"
                     + "startTime INTEGER);");
 
             db.execSQL("CREATE INDEX historyEventTime ON history (eventTime)");
@@ -206,10 +216,27 @@ public class SyncStorageEngine {
 
         @Override
         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-            if (oldVersion == 9 && newVersion == 10) {
+            if (oldVersion == 9) {
                 Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
                         + newVersion + ", which will preserve old data");
                 db.execSQL("ALTER TABLE status ADD COLUMN initialFailureTime INTEGER");
+                oldVersion++;
+            }
+
+            if (oldVersion == 10) {
+                Log.w(TAG, "Upgrading database from version " + oldVersion + " to "
+                        + newVersion + ", which will preserve old data");
+                db.execSQL("ALTER TABLE pending ADD COLUMN account_type TEXT");
+                db.execSQL("ALTER TABLE stats ADD COLUMN account_type TEXT");
+                db.execSQL("ALTER TABLE active ADD COLUMN account_type TEXT");
+
+                db.execSQL("UPDATE pending SET account_type='com.google.GAIA'");
+                db.execSQL("UPDATE stats SET account_type='com.google.GAIA'");
+                db.execSQL("UPDATE active SET account_type='com.google.GAIA'");
+                oldVersion++;
+            }
+
+            if (oldVersion == newVersion) {
                 return;
             }
 
@@ -233,23 +260,23 @@ public class SyncStorageEngine {
         }
     }
 
-    protected void doDatabaseCleanup(String[] accounts) {
-        HashSet<String> currentAccounts = new HashSet<String>();
-        for (String account : accounts) currentAccounts.add(account);
+    protected void doDatabaseCleanup(Account[] accounts) {
+        HashSet<Account> currentAccounts = Sets.newHashSet(accounts);
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         Cursor cursor = db.query("stats", STATS_ACCOUNT_PROJECTION,
-                null /* where */, null /* where args */, Sync.Stats.ACCOUNT,
+                null /* where */, null /* where args */,
+                Sync.Stats.ACCOUNT + "," + Sync.Stats.ACCOUNT_TYPE,
                 null /* having */, null /* order by */);
         try {
             while (cursor.moveToNext()) {
-                String account = cursor.getString(0);
-                if (TextUtils.isEmpty(account)) {
-                    continue;
-                }
+                String accountName = cursor.getString(0);
+                String accountType = cursor.getString(1);
+                final Account account = new Account(accountName, accountType);
                 if (!currentAccounts.contains(account)) {
-                    String where = Sync.Stats.ACCOUNT + "=?";
+                    String where = Sync.Stats.ACCOUNT + "=? AND " + Sync.Stats.ACCOUNT_TYPE + "=?";
                     int numDeleted;
-                    numDeleted = db.delete("stats", where, new String[]{account});
+                    numDeleted = db.delete("stats", where,
+                            new String[]{account.mName, account.mType});
                     if (Config.LOGD) {
                         Log.d(TAG, "deleted " + numDeleted
                                 + " records from stats table"
@@ -272,10 +299,11 @@ public class SyncStorageEngine {
         }
     }
 
-    private int updateActiveSync(String account, String authority, Long startTime) {
+    private int updateActiveSync(Account account, String authority, Long startTime) {
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         ContentValues values = new ContentValues();
-        values.put("account", account);
+        values.put("account", account == null ? null : account.mName);
+        values.put("account_type", account == null ? null : account.mType);
         values.put("authority", authority);
         values.put("startTime", startTime);
         int numChanges = db.update("active", values, null, null);
@@ -463,7 +491,9 @@ public class SyncStorageEngine {
             db.beginTransaction();
             long rowId = db.insert("pending", Sync.Pending.ACCOUNT, values);
             if (rowId < 0) return null;
-            String account = values.getAsString(Sync.Pending.ACCOUNT);
+            String accountName = values.getAsString(Sync.Pending.ACCOUNT);
+            String accountType = values.getAsString(Sync.Pending.ACCOUNT_TYPE);
+            final Account account = new Account(accountName, accountType);
             String authority = values.getAsString(Sync.Pending.AUTHORITY);
 
             long statsId = createStatsRowIfNecessary(account, authority);
@@ -491,25 +521,31 @@ public class SyncStorageEngine {
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         db.beginTransaction();
         try {
-            String account;
+            Account account;
             String authority;
             Cursor c = db.query("pending",
-                    new String[]{Sync.Pending.ACCOUNT, Sync.Pending.AUTHORITY},
+                    new String[]{Sync.Pending.ACCOUNT, Sync.Pending.ACCOUNT_TYPE,
+                            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);
+                String accountName = c.getString(0);
+                String accountType = c.getString(1);
+                account = new Account(accountName, accountType);
+                authority = c.getString(2);
             } finally {
                 c.close();
             }
             db.delete("pending", "_id=" + rowId, null /* no where args */);
-            final String[] accountAuthorityWhereArgs = new String[]{account, authority};
+            final String[] accountAuthorityWhereArgs =
+                    new String[]{account.mName, account.mType, authority};
             boolean isPending = 0 < DatabaseUtils.longForQuery(db,
-                    "SELECT COUNT(*) FROM PENDING WHERE account=? AND authority=?",
+                    "SELECT COUNT(*)"
+                            + " FROM PENDING"
+                            + " WHERE account=? AND account_type=? AND authority=?",
                     accountAuthorityWhereArgs);
             if (!isPending) {
                 long statsId = createStatsRowIfNecessary(account, authority);
@@ -581,7 +617,7 @@ public class SyncStorageEngine {
         return numDeletes > 0;
     }
 
-    public long insertStartSyncEvent(String account, String authority, long now, int source) {
+    public long insertStartSyncEvent(Account account, String authority, long now, int source) {
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         long statsId = createStatsRowIfNecessary(account, authority);
 
@@ -731,14 +767,15 @@ public class SyncStorageEngine {
         }
     }
 
-    private long createStatsRowIfNecessary(String account, String authority) {
+    private long createStatsRowIfNecessary(Account account, String authority) {
         SQLiteDatabase db = mOpenHelper.getWritableDatabase();
         StringBuilder where = new StringBuilder();
         where.append(Sync.Stats.ACCOUNT + "= ?");
+        where.append(" and " + Sync.Stats.ACCOUNT_TYPE + "= ?");
         where.append(" and " + Sync.Stats.AUTHORITY + "= ?");
         Cursor cursor = query(Sync.Stats.CONTENT_URI,
                 Sync.Stats.SYNC_STATS_PROJECTION,
-                where.toString(), new String[] { account, authority },
+                where.toString(), new String[] { account.mName, account.mType, authority },
                 null /* order */);
         try {
             long id;
@@ -746,7 +783,8 @@ public class SyncStorageEngine {
                 id = cursor.getLong(cursor.getColumnIndexOrThrow(Sync.Stats._ID));
             } else {
                 ContentValues values = new ContentValues();
-                values.put(Sync.Stats.ACCOUNT, account);
+                values.put(Sync.Stats.ACCOUNT, account.mName);
+                values.put(Sync.Stats.ACCOUNT_TYPE, account.mType);
                 values.put(Sync.Stats.AUTHORITY, authority);
                 id = db.insert("stats", null, values);
             }
index e0cd786..93ebbea 100644 (file)
@@ -19,6 +19,7 @@ package android.content;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.net.Uri;
+import android.accounts.Account;
 
 import java.util.Map;
 
@@ -110,7 +111,7 @@ public abstract class SyncableContentProvider extends ContentProvider {
      * @param context the sync context for the operation
      * @param account
      */
-    public abstract void onSyncStart(SyncContext context, String account);
+    public abstract void onSyncStart(SyncContext context, Account account);
 
     /**
      * Called right after a sync is completed
@@ -124,7 +125,7 @@ public abstract class SyncableContentProvider extends ContentProvider {
      * The account of the most recent call to onSyncStart()
      * @return the account
      */
-    public abstract String getSyncingAccount();
+    public abstract Account getSyncingAccount();
 
     /**
      * Merge diffs from a sync source with this content provider.
@@ -194,7 +195,7 @@ public abstract class SyncableContentProvider extends ContentProvider {
      * Make sure that there are no entries for accounts that no longer exist
      * @param accountsArray the array of currently-existing accounts
      */
-    protected abstract void onAccountsChanged(String[] accountsArray);
+    protected abstract void onAccountsChanged(Account[] accountsArray);
 
     /**
      * A helper method to delete all rows whose account is not in the accounts
@@ -203,26 +204,24 @@ public abstract class SyncableContentProvider extends ContentProvider {
      *
      * @param accounts a map of existing accounts
      * @param table the table to delete from
-     * @param accountColumnName the name of the column that is expected
-     * to hold the account.
      */
-    protected abstract void deleteRowsForRemovedAccounts(Map<String, Boolean> accounts,
-            String table, String accountColumnName);
+    protected abstract void deleteRowsForRemovedAccounts(Map<Account, Boolean> accounts,
+            String table);
 
     /**
      * Called when the sync system determines that this provider should no longer
      * contain records for the specified account.
      */
-    public abstract void wipeAccount(String account);
+    public abstract void wipeAccount(Account account);
 
     /**
      * Retrieves the SyncData bytes for the given account. The byte array returned may be null.
      */
-    public abstract byte[] readSyncDataBytes(String account);
+    public abstract byte[] readSyncDataBytes(Account account);
 
     /**
      * Sets the SyncData bytes for the given account. The bytes array may be null.
      */
-    public abstract void writeSyncDataBytes(String account, byte[] data);
+    public abstract void writeSyncDataBytes(Account account, byte[] data);
 }
 
index eb3a5da..0cbe01e 100644 (file)
@@ -12,6 +12,7 @@ import android.util.Config;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.TimingLogger;
+import android.accounts.Account;
 
 /**
  * @hide
@@ -67,7 +68,7 @@ public abstract class TempProviderSyncAdapter extends SyncAdapter {
      * @return true, if the sync was successfully started. One reason it can
      *   fail to start is if there is no user configured on the device.
      */
-    public abstract void onSyncStarting(SyncContext context, String account, boolean forced,
+    public abstract void onSyncStarting(SyncContext context, Account account, boolean forced,
             SyncResult result);
 
     /**
@@ -168,12 +169,12 @@ public abstract class TempProviderSyncAdapter extends SyncAdapter {
      * exist.
      * @param accounts the list of accounts
      */
-    public abstract void onAccountsChanged(String[] accounts);
+    public abstract void onAccountsChanged(Account[] accounts);
 
     private Context mContext;
 
     private class SyncThread extends Thread {
-        private final String mAccount;
+        private final Account mAccount;
         private final Bundle mExtras;
         private final SyncContext mSyncContext;
         private volatile boolean mIsCanceled = false;
@@ -181,7 +182,7 @@ public abstract class TempProviderSyncAdapter extends SyncAdapter {
         private long mInitialRxBytes;
         private final SyncResult mResult;
 
-        SyncThread(SyncContext syncContext, String account, Bundle extras) {
+        SyncThread(SyncContext syncContext, Account account, Bundle extras) {
             super("SyncThread");
             mAccount = account;
             mExtras = extras;
@@ -221,7 +222,7 @@ public abstract class TempProviderSyncAdapter extends SyncAdapter {
             }
         }
 
-        private void sync(SyncContext syncContext, String account, Bundle extras) {
+        private void sync(SyncContext syncContext, Account account, Bundle extras) {
             mIsCanceled = false;
 
             mProviderSyncStarted = false;
@@ -273,7 +274,7 @@ public abstract class TempProviderSyncAdapter extends SyncAdapter {
             }
         }
 
-        private void runSyncLoop(SyncContext syncContext, String account, Bundle extras) {
+        private void runSyncLoop(SyncContext syncContext, Account account, Bundle extras) {
             TimingLogger syncTimer = new TimingLogger(TAG + "Profiling", "sync");
             syncTimer.addSplit("start");
             int loopCount = 0;
@@ -518,7 +519,7 @@ public abstract class TempProviderSyncAdapter extends SyncAdapter {
         EventLog.writeEvent(SyncAdapter.LOG_SYNC_DETAILS, TAG, bytesSent, bytesReceived, "");
     }
 
-    public void startSync(SyncContext syncContext, String account, Bundle extras) {
+    public void startSync(SyncContext syncContext, Account account, Bundle extras) {
         if (mSyncThread != null) {
             syncContext.onFinished(SyncResult.ALREADY_IN_PROGRESS);
             return;
index 4a709f6..3a221e4 100644 (file)
@@ -32,6 +32,7 @@ import android.text.format.DateUtils;
 import android.text.format.Time;
 import android.util.Config;
 import android.util.Log;
+import android.accounts.Account;
 import com.android.internal.database.ArrayListCursor;
 import com.google.android.gdata.client.AndroidGDataClient;
 import com.google.android.gdata.client.AndroidXmlParserFactory;
@@ -157,11 +158,12 @@ public final class Calendar {
          * @param account the account whose rows should be deleted
          * @return the count of rows that were deleted
          */
-        public static int deleteCalendarsForAccount(ContentResolver cr,
-                String account) {
+        public static int deleteCalendarsForAccount(ContentResolver cr, Account account) {
             // delete all calendars that match this account
-            return Calendar.Calendars.delete(cr, Calendar.Calendars._SYNC_ACCOUNT + "=?",
-                    new String[] {account});
+            return Calendar.Calendars.delete(cr,
+                    Calendar.Calendars._SYNC_ACCOUNT + "=? AND "
+                            + Calendar.Calendars._SYNC_ACCOUNT_TYPE + "=?",
+                    new String[] {account.mName, account.mType});
         }
 
         /**
index 064ed88..a6450f3 100644 (file)
@@ -30,6 +30,7 @@ import android.net.Uri;
 import android.text.TextUtils;
 import android.util.Log;
 import android.widget.ImageView;
+import android.accounts.Account;
 
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
@@ -75,6 +76,12 @@ public class Contacts {
         public static final String _SYNC_ACCOUNT = "_sync_account";
 
         /**
+         * The _SYNC_ACCOUNT_TYPE to which this setting corresponds. This may be null.
+         * <P>Type: TEXT</P>
+         */
+        public static final String _SYNC_ACCOUNT_TYPE = "_sync_account_type";
+
+        /**
          * The key of this setting.
          * <P>Type: TEXT</P>
          */
@@ -134,6 +141,7 @@ public class Contacts {
                 selectString = (account == null)
                         ? "_sync_account is null AND key=?"
                         : "_sync_account=? AND key=?";
+//                : "_sync_account=? AND _sync_account_type=? AND key=?";
                 selectArgs = (account == null)
                 ? new String[]{key}
                 : new String[]{account, key};
@@ -158,7 +166,8 @@ public class Contacts {
             // the account name is, so we're using a global setting for SYNC_EVERYTHING.
             // Some day when we add multiple accounts to the UI this should honor the account
             // that was asked for.
-            //values.put(_SYNC_ACCOUNT, account);
+            //values.put(_SYNC_ACCOUNT, account.mName);
+            //values.put(_SYNC_ACCOUNT_TYPE, account.mType);
             values.put(KEY, key);
             values.put(VALUE, value);
             cr.update(Settings.CONTENT_URI, values, null, null);
@@ -840,6 +849,12 @@ public class Contacts {
         public static final String GROUP_SYNC_ACCOUNT = "group_sync_account";
 
         /**
+         * The account type of the group.
+         * <P>Type: TEXT</P>
+         */
+        public static final String GROUP_SYNC_ACCOUNT_TYPE = "group_sync_account_type";
+
+        /**
          * The row id of the person.
          * <P>Type: TEXT</P>
          */
index 4d430d5..f94b442 100644 (file)
@@ -20,6 +20,7 @@ import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.net.Uri;
+import android.accounts.Account;
 
 /**
  * The SubscribedFeeds provider stores all information about subscribed feeds.
@@ -99,7 +100,7 @@ public class SubscribedFeeds {
         /**
          * The default sort order for this table
          */
-        public static final String DEFAULT_SORT_ORDER = "_SYNC_ACCOUNT ASC";
+        public static final String DEFAULT_SORT_ORDER = "_SYNC_ACCOUNT_TYPE, _SYNC_ACCOUNT ASC";
     }
 
     /**
@@ -114,38 +115,36 @@ public class SubscribedFeeds {
      * @return  the Uri of the feed that was added
      */
     public static Uri addFeed(ContentResolver resolver,
-            String feed, String account,
+            String feed, Account account,
             String authority, String service) {
         ContentValues values = new ContentValues();
         values.put(SubscribedFeeds.Feeds.FEED, feed);
-        values.put(SubscribedFeeds.Feeds._SYNC_ACCOUNT, account);
+        values.put(SubscribedFeeds.Feeds._SYNC_ACCOUNT, account.mName);
+        values.put(SubscribedFeeds.Feeds._SYNC_ACCOUNT_TYPE, account.mType);
         values.put(SubscribedFeeds.Feeds.AUTHORITY, authority);
         values.put(SubscribedFeeds.Feeds.SERVICE, service);
         return resolver.insert(SubscribedFeeds.Feeds.CONTENT_URI, values);
     }
 
     public static int deleteFeed(ContentResolver resolver,
-            String feed, String account, String authority) {
+            String feed, Account account, String authority) {
         StringBuilder where = new StringBuilder();
         where.append(SubscribedFeeds.Feeds._SYNC_ACCOUNT + "=?");
+        where.append(" AND " + SubscribedFeeds.Feeds._SYNC_ACCOUNT_TYPE + "=?");
         where.append(" AND " + SubscribedFeeds.Feeds.FEED + "=?");
         where.append(" AND " + SubscribedFeeds.Feeds.AUTHORITY + "=?");
         return resolver.delete(SubscribedFeeds.Feeds.CONTENT_URI,
-                where.toString(), new String[] {account, feed, authority});
+                where.toString(), new String[] {account.mName, account.mType, feed, authority});
     }
 
     public static int deleteFeeds(ContentResolver resolver,
-            String account, String authority) {
+            Account account, String authority) {
         StringBuilder where = new StringBuilder();
         where.append(SubscribedFeeds.Feeds._SYNC_ACCOUNT + "=?");
+        where.append(" AND " + SubscribedFeeds.Feeds._SYNC_ACCOUNT_TYPE + "=?");
         where.append(" AND " + SubscribedFeeds.Feeds.AUTHORITY + "=?");
         return resolver.delete(SubscribedFeeds.Feeds.CONTENT_URI,
-                where.toString(), new String[] {account, authority});
-    }
-
-    public static String gtalkServiceRoutingInfoFromAccountAndResource(
-            String account, String res) {
-        return Uri.parse("gtalk://" + account + "/" + res).toString();
+                where.toString(), new String[] {account.mName, account.mType, authority});
     }
 
     /**
@@ -157,6 +156,12 @@ public class SubscribedFeeds {
          * <P>Type: TEXT</P>
          */
         public static final String _SYNC_ACCOUNT = SyncConstValue._SYNC_ACCOUNT;
+
+        /**
+         * The account type.
+         * <P>Type: TEXT</P>
+         */
+        public static final String _SYNC_ACCOUNT_TYPE = SyncConstValue._SYNC_ACCOUNT_TYPE;
     }
 
     /**
@@ -199,6 +204,6 @@ public class SubscribedFeeds {
         /**
          * The default sort order for this table
          */
-        public static final String DEFAULT_SORT_ORDER = "_SYNC_ACCOUNT ASC";
+        public static final String DEFAULT_SORT_ORDER = "_SYNC_ACCOUNT_TYPE, _SYNC_ACCOUNT ASC";
     }
 }
index 628852f..c9bde0e 100644 (file)
@@ -22,6 +22,8 @@ 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;
 
@@ -51,6 +53,12 @@ public final class Sync {
         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>
          */
@@ -280,7 +288,8 @@ public final class Sync {
         public static final String MESG_CANCELED = "canceled";
 
         private static final String FINISHED_SINCE_WHERE_CLAUSE = EVENT + "=" + EVENT_STOP
-                + " AND " + EVENT_TIME + ">? AND " + ACCOUNT + "=? AND " + AUTHORITY + "=?";
+                + " AND " + EVENT_TIME + ">? AND " + ACCOUNT + "=? AND " + ACCOUNT_TYPE + "=?"
+                + " AND " + AUTHORITY + "=?";
 
         public static String mesgToString(String mesg) {
             if (MESG_SUCCESS.equals(mesg)) return mesg;
@@ -311,10 +320,11 @@ public final class Sync {
         }
 
         public static boolean hasNewerSyncFinished(ContentResolver contentResolver,
-                String account, String authority, long when) {
+                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, authority}, null);
+                    new String[]{Long.toString(when), account.mName, account.mType, authority},
+                    null);
             try {
               return c.getCount() > 0;
             } finally {
@@ -345,7 +355,8 @@ public final class Sync {
          * @return the cursor on the AuthorityHistory table
          */
         public static Cursor query(ContentResolver contentResolver) {
-            return contentResolver.query(CONTENT_URI, null, null, null, ACCOUNT + ", " + AUTHORITY);
+            return contentResolver.query(CONTENT_URI, null, null, null,
+                    ACCOUNT_TYPE + "," + ACCOUNT + "," + AUTHORITY);
         }
 
         public static class QueryMap extends ContentQueryMap {
@@ -356,10 +367,11 @@ public final class Sync {
                         _ID, keepUpdated, handlerForUpdateNotifications);
             }
 
-            public ContentValues get(String account, String authority) {
+            public ContentValues get(Account account, String authority) {
                 Map<String, ContentValues> rows = getRows();
                 for (ContentValues values : rows.values()) {
-                    if (values.getAsString(ACCOUNT).equals(account)
+                    if (values.getAsString(ACCOUNT).equals(account.mName)
+                            && values.getAsString(ACCOUNT_TYPE).equals(account.mType)
                             && values.getAsString(AUTHORITY).equals(authority)) {
                         return values;
                     }
@@ -390,10 +402,11 @@ public final class Sync {
                         handlerForUpdateNotifications);
             }
 
-            public boolean isPending(String account, String authority) {
+            public boolean isPending(Account account, String authority) {
                 Map<String, ContentValues> rows = getRows();
                 for (ContentValues values : rows.values()) {
-                    if (values.getAsString(ACCOUNT).equals(account)
+                    if (values.getAsString(ACCOUNT).equals(account.mName)
+                            && values.getAsString(ACCOUNT_TYPE).equals(account.mType)
                             && values.getAsString(AUTHORITY).equals(authority)) {
                         return true;
                     }
@@ -444,9 +457,12 @@ public final class Sync {
                 return null;
             }
 
-            public String getSyncingAccount() {
+            public Account getSyncingAccount() {
                 ContentValues values = getActiveSyncInfo();
-                return (values == null) ? null : values.getAsString(ACCOUNT);
+                if (values == null || TextUtils.isEmpty(values.getAsString(ACCOUNT))) {
+                    return null;
+                }
+                return new Account(values.getAsString(ACCOUNT), values.getAsString(ACCOUNT_TYPE));
             }
 
             public String getSyncingAuthority() {
index 6eb4398..30966eb 100644 (file)
@@ -29,6 +29,12 @@ public interface SyncConstValue
     public static final String _SYNC_ACCOUNT = "_sync_account";
 
     /**
+     * The type of the account that was used to sync the entry to the device.
+     * <P>Type: TEXT</P>
+     */
+    public static final String _SYNC_ACCOUNT_TYPE = "_sync_account_type";
+
+    /**
      * The unique ID for a row assigned by the sync source. NULL if the row has never been synced.
      * <P>Type: TEXT</P>
      */
@@ -68,4 +74,9 @@ public interface SyncConstValue
      * Used to indicate that this account is not synced
      */
     public static final String NON_SYNCABLE_ACCOUNT = "non_syncable";
+
+    /**
+     * Used to indicate that this account is not synced
+     */
+    public static final String NON_SYNCABLE_ACCOUNT_TYPE = "android.local";
 }
index 8268fee..de7b1d7 100644 (file)
@@ -17,13 +17,15 @@ import android.database.sqlite.SQLiteFullException;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.os.Bundle;
-import android.os.Debug;
 import android.text.TextUtils;
 import android.net.Uri;
+import android.accounts.Account;
 
 import java.util.ArrayList;
 import java.util.Calendar;
 
+import com.google.android.collect.Lists;
+
 /**
  * A service to handle various intents asynchronously.
  */
@@ -31,7 +33,8 @@ public class SubscribedFeedsIntentService extends IntentService {
     private static final String TAG = "Sync";
 
     private static final String[] sAccountProjection =
-            new String[] {SubscribedFeeds.Accounts._SYNC_ACCOUNT};
+            new String[] {SubscribedFeeds.Accounts._SYNC_ACCOUNT,
+                    SubscribedFeeds.Accounts._SYNC_ACCOUNT_TYPE};
 
     /** How often to refresh the subscriptions, in milliseconds */
     private static final long SUBSCRIPTION_REFRESH_INTERVAL = 1000L * 60 * 60 * 24; // one day
@@ -56,10 +59,10 @@ public class SubscribedFeedsIntentService extends IntentService {
         if (GTALK_DATA_MESSAGE_RECEIVED.equals(intent.getAction())) {
             boolean fromTrustedServer = intent.getBooleanExtra("from_trusted_server", false);
             if (fromTrustedServer) {
-                String account = intent.getStringExtra("account");
+                String accountName = intent.getStringExtra("account");
                 String token = intent.getStringExtra("message_token");
 
-                if (TextUtils.isEmpty(account) || TextUtils.isEmpty(token)) {
+                if (TextUtils.isEmpty(accountName) || TextUtils.isEmpty(token)) {
                     if (Config.LOGD) {
                         Log.d(TAG, "Ignoring malformed tickle -- missing account or token.");
                     }
@@ -68,10 +71,10 @@ public class SubscribedFeedsIntentService extends IntentService {
 
                 if (Config.LOGD) {
                     Log.d(TAG, "Received network tickle for "
-                            + account + " - " + token);
+                            + accountName + " - " + token);
                 }
 
-                handleTickle(this, account, token);
+                handleTickle(this, accountName, token);
             } else {
                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
                     Log.v(TAG, "Ignoring tickle -- not from trusted server.");
@@ -103,20 +106,22 @@ public class SubscribedFeedsIntentService extends IntentService {
         alarmManager.set(AlarmManager.RTC, when, pendingIntent);
     }
 
-    private void handleTickle(Context context, String account, String feed) {
+    private void handleTickle(Context context, String accountName, String feed) {
         Cursor c = null;
         Sync.Settings.QueryMap syncSettings =
                 new Sync.Settings.QueryMap(context.getContentResolver(),
                         false /* don't keep updated */,
                         null /* not needed since keep updated is false */);
         final String where = SubscribedFeeds.Feeds._SYNC_ACCOUNT + "= ? "
+                + "and " + SubscribedFeeds.Feeds._SYNC_ACCOUNT_TYPE + "= ? "
                 + "and " + SubscribedFeeds.Feeds.FEED + "= ?";
         try {
+            // TODO(fredq) fix the hardcoded type
             c = context.getContentResolver().query(SubscribedFeeds.Feeds.CONTENT_URI,
-                    null, where, new String[]{account, feed}, null);
+                    null, where, new String[]{accountName, "com.google.GAIA", feed}, null);
             if (c.getCount() == 0) {
                 Log.w(TAG, "received tickle for non-existent feed: "
-                        + "account " + account + ", feed " + feed);
+                        + "account " + accountName + ", feed " + feed);
                 EventLog.writeEvent(LOG_TICKLE, "unknown");
             }
             while (c.moveToNext()) {
@@ -131,7 +136,8 @@ public class SubscribedFeedsIntentService extends IntentService {
                 }
                 Uri uri = Uri.parse("content://" + authority);
                 Bundle extras = new Bundle();
-                extras.putString(ContentResolver.SYNC_EXTRAS_ACCOUNT, account);
+                extras.putParcelable(ContentResolver.SYNC_EXTRAS_ACCOUNT,
+                        new Account(accountName, "com.google.GAIA"));
                 extras.putString("feed", feed);
                 context.getContentResolver().startSync(uri, extras);
             }
@@ -151,17 +157,15 @@ public class SubscribedFeedsIntentService extends IntentService {
      */
     private void handleRefreshAlarm(Context context) {
         // retrieve the list of accounts from the subscribed feeds
-        ArrayList<String> accounts = new ArrayList<String>();
+        ArrayList<Account> accounts = Lists.newArrayList();
         ContentResolver contentResolver = context.getContentResolver();
         Cursor c = contentResolver.query(SubscribedFeeds.Accounts.CONTENT_URI,
                 sAccountProjection, null, null, null);
         try {
             while (c.moveToNext()) {
-                String account = c.getString(0);
-                if (TextUtils.isEmpty(account)) {
-                    continue;
-                }
-                accounts.add(account);
+                String accountName = c.getString(0);
+                String accountType = c.getString(1);
+                accounts.add(new Account(accountName, accountType));
             }
         } finally {
             c.close();
@@ -169,16 +173,19 @@ public class SubscribedFeedsIntentService extends IntentService {
 
         // Clear the auth tokens for all these accounts so that we are sure
         // they will still be valid until the next time we refresh them.
-        // TODO: add this when the google login service is done
+        // TODO(fredq): add this when the google login service is done
 
         // mark the feeds dirty, by setting the accounts to the same value,
         //  which will trigger a sync.
         try {
             ContentValues values = new ContentValues();
-            for (String account : accounts) {
-                values.put(SyncConstValue._SYNC_ACCOUNT, account);
+            for (Account account : accounts) {
+                values.put(SyncConstValue._SYNC_ACCOUNT, account.mName);
+                values.put(SyncConstValue._SYNC_ACCOUNT_TYPE, account.mType);
                 contentResolver.update(SubscribedFeeds.Feeds.CONTENT_URI, values,
-                        SubscribedFeeds.Feeds._SYNC_ACCOUNT + "=?", new String[] {account});
+                        SubscribedFeeds.Feeds._SYNC_ACCOUNT + "=? AND "
+                                + SubscribedFeeds.Feeds._SYNC_ACCOUNT_TYPE + "=?",
+                        new String[] {account.mName, account.mType});
             }
         } catch (SQLiteFullException e) {
             Log.w(TAG, "disk full while trying to mark the feeds as dirty, skipping");
index 9ecc3d6..d87f5e7 100644 (file)
@@ -39,7 +39,7 @@ import java.util.HashMap;
 public class SubscribedFeedsProvider extends AbstractSyncableContentProvider {
     private static final String TAG = "SubscribedFeedsProvider";
     private static final String DATABASE_NAME = "subscribedfeeds.db";
-    private static final int DATABASE_VERSION = 10;
+    private static final int DATABASE_VERSION = 11;
 
     private static final int FEEDS = 1;
     private static final int FEED_ID = 2;
@@ -88,6 +88,7 @@ public class SubscribedFeedsProvider extends AbstractSyncableContentProvider {
         db.execSQL("CREATE TABLE feeds (" +
                     "_id INTEGER PRIMARY KEY," +
                     "_sync_account TEXT," + // From the sync source
+                    "_sync_account_type TEXT," + // From the sync source
                     "_sync_id TEXT," + // From the sync source
                     "_sync_time TEXT," + // From the sync source
                     "_sync_version TEXT," + // From the sync source
@@ -106,8 +107,8 @@ public class SubscribedFeedsProvider extends AbstractSyncableContentProvider {
                     "WHEN old._sync_id is not null " +
                     "BEGIN " +
                         "INSERT INTO _deleted_feeds " +
-                            "(_sync_id, _sync_account, _sync_version) " +
-                            "VALUES (old._sync_id, old._sync_account, " +
+                            "(_sync_id, _sync_account, _sync_account_type, _sync_version) " +
+                            "VALUES (old._sync_id, old._sync_account, old._sync_account_type, " +
                             "old._sync_version);" +
                     "END");
 
@@ -116,6 +117,7 @@ public class SubscribedFeedsProvider extends AbstractSyncableContentProvider {
                     "_sync_id TEXT," +
                     (isTemporary() ? "_sync_local_id INTEGER," : "") + // Used while syncing,
                     "_sync_account TEXT," +
+                    "_sync_account_type TEXT," +
                     "_sync_mark INTEGER, " + // Used to filter out new rows
                     "UNIQUE(_sync_id))");
     }
@@ -170,7 +172,8 @@ public class SubscribedFeedsProvider extends AbstractSyncableContentProvider {
                 qb.setDistinct(true);
                 qb.setProjectionMap(ACCOUNTS_PROJECTION_MAP);
                 return qb.query(getDatabase(), projection, selection, selectionArgs,
-                        SubscribedFeeds.Feeds._SYNC_ACCOUNT, null, sortOrder);
+                        SubscribedFeeds.Feeds._SYNC_ACCOUNT_TYPE + ","
+                                + SubscribedFeeds.Feeds._SYNC_ACCOUNT, null, sortOrder);
             case FEED_ID:
                 qb.setTables(sFeedsTable);
                 qb.appendWhere(sFeedsTable + "._id=");
@@ -310,6 +313,8 @@ public class SubscribedFeedsProvider extends AbstractSyncableContentProvider {
             DatabaseUtils.cursorStringToContentValues(diffsCursor,
                     SubscribedFeeds.Feeds._SYNC_ACCOUNT, mValues);
             DatabaseUtils.cursorStringToContentValues(diffsCursor,
+                    SubscribedFeeds.Feeds._SYNC_ACCOUNT_TYPE, mValues);
+            DatabaseUtils.cursorStringToContentValues(diffsCursor,
                     SubscribedFeeds.Feeds._SYNC_VERSION, mValues);
             db.replace(mDeletedTable, SubscribedFeeds.Feeds._SYNC_ID, mValues);
         }
@@ -369,5 +374,7 @@ public class SubscribedFeedsProvider extends AbstractSyncableContentProvider {
         ACCOUNTS_PROJECTION_MAP = map;
         map.put(SubscribedFeeds.Accounts._COUNT, "COUNT(*) AS _count");
         map.put(SubscribedFeeds.Accounts._SYNC_ACCOUNT, SubscribedFeeds.Accounts._SYNC_ACCOUNT);
+        map.put(SubscribedFeeds.Accounts._SYNC_ACCOUNT_TYPE,
+                SubscribedFeeds.Accounts._SYNC_ACCOUNT_TYPE);
     }
 }
index fc29906..d1360eb 100644 (file)
@@ -1,6 +1,5 @@
 # Classes which are preloaded by com.android.internal.os.ZygoteInit.
 android.R$styleable
-android.accounts.AccountMonitor
 android.app.Activity
 android.app.ActivityGroup
 android.app.ActivityManager$MemoryInfo$1
index c1d2507..0b4bfed 100644 (file)
@@ -23,6 +23,8 @@ import android.os.Bundle;
 import android.os.SystemClock;
 import android.provider.Sync;
 import android.net.Uri;
+import android.accounts.Account;
+
 import java.util.Map;
 
 /**
@@ -49,7 +51,8 @@ public class SyncBaseInstrumentation extends InstrumentationTestCase {
     protected void syncProvider(Uri uri, String account, String authority) throws Exception {
         Bundle extras = new Bundle();
         extras.putBoolean(ContentResolver.SYNC_EXTRAS_FORCE, true);
-        extras.putString(ContentResolver.SYNC_EXTRAS_ACCOUNT, account);
+        Account account1 = new Account(account, "com.google.GAIA");
+        extras.putParcelable(ContentResolver.SYNC_EXTRAS_ACCOUNT, account1);
 
         mContentResolver.startSync(uri, extras);
         long startTimeInMillis = SystemClock.elapsedRealtime();
@@ -66,7 +69,7 @@ public class SyncBaseInstrumentation extends InstrumentationTestCase {
                 break;
             }
 
-            if (isSyncActive(account, authority)) {
+            if (isSyncActive(account1, authority)) {
                 counter = 0;
                 continue;
             }
@@ -87,7 +90,7 @@ public class SyncBaseInstrumentation extends InstrumentationTestCase {
      * entry is in either the Pending or Active tables.
      * @return
      */
-    private boolean isSyncActive(String account, String authority) {
+    private boolean isSyncActive(Account account, String authority) {
         Sync.Pending.QueryMap pendingQueryMap = null;
         Sync.Active.QueryMap activeQueryMap = null;
         try {
@@ -107,11 +110,12 @@ public class SyncBaseInstrumentation extends InstrumentationTestCase {
         }
     }
 
-    private boolean isActiveInActiveQueryMap(Sync.Active.QueryMap activemap, String account,
+    private boolean isActiveInActiveQueryMap(Sync.Active.QueryMap activemap, Account account,
                                              String authority) {
         Map<String, ContentValues> rows = activemap.getRows();
         for (ContentValues values : rows.values()) {
-            if (values.getAsString("account").equals(account)
+            if (values.getAsString("account").equals(account.mName)
+                    && values.getAsString("account_type").equals(account.mType)
                     && values.getAsString("authority").equals(authority)) {
                 return true;
             }
index 36805b1..27f1b8b 100644 (file)
@@ -21,6 +21,7 @@ import android.test.RenamingDelegatingContext;
 import android.test.mock.MockContext;
 import android.test.mock.MockContentResolver;
 import android.provider.Sync;
+import android.accounts.Account;
 
 public class SyncStorageEngineTest extends AndroidTestCase {
 
@@ -29,7 +30,7 @@ public class SyncStorageEngineTest extends AndroidTestCase {
      * correcponding sync is finished. This can happen if the clock changes while we are syncing.
      */
     public void testPurgeActiveSync() throws Exception {
-        final String account = "a@example.com";
+        final Account account = new Account("a@example.com", "example.type");
         final String authority = "testprovider";
 
         MockContentResolver mockResolver = new MockContentResolver();
index aa3d186..42c1e78 100644 (file)
@@ -8,6 +8,7 @@ import android.database.sqlite.SQLiteDatabase;
 import android.net.Uri;
 import android.test.AndroidTestCase;
 import android.text.TextUtils;
+import android.accounts.Account;
 
 import java.util.ArrayList;
 import java.util.Map;
@@ -26,7 +27,7 @@ public class AbstractTableMergerTest extends AndroidTestCase {
     static final Uri TABLE_URI = Uri.withAppendedPath(CONTENT_URI, TABLE_NAME);
     static final Uri DELETED_TABLE_URI = Uri.withAppendedPath(CONTENT_URI, DELETED_TABLE_NAME);
 
-    private final String ACCOUNT = "account@goo.com";
+    private final Account ACCOUNT = new Account("account@goo.com", "example.type");
 
     private final ArrayList<Expectation> mExpectations = Lists.newArrayList();
 
@@ -65,25 +66,31 @@ public class AbstractTableMergerTest extends AndroidTestCase {
         mExpectations.clear();
     }
 
-    ContentValues newValues(String data, String syncId, String syncAccount,
+    ContentValues newValues(String data, String syncId, Account syncAccount,
             String syncTime, String syncVersion, Long syncLocalId) {
         ContentValues values = new ContentValues();
         if (data != null) values.put("data", data);
         if (syncTime != null) values.put("_sync_time", syncTime);
         if (syncVersion != null) values.put("_sync_version", syncVersion);
         if (syncId != null) values.put("_sync_id", syncId);
-        if (syncAccount != null) values.put("_sync_account", syncAccount);
+        if (syncAccount != null) {
+            values.put("_sync_account", syncAccount.mName);
+            values.put("_sync_account_type", syncAccount.mType);
+        }
         values.put("_sync_local_id", syncLocalId);
         values.put("_sync_dirty", 0);
         return values;
     }
 
-    ContentValues newDeletedValues(String syncId, String syncAccount, String syncVersion,
+    ContentValues newDeletedValues(String syncId, Account syncAccount, String syncVersion,
             Long syncLocalId) {
         ContentValues values = new ContentValues();
         if (syncVersion != null) values.put("_sync_version", syncVersion);
         if (syncId != null) values.put("_sync_id", syncId);
-        if (syncAccount != null) values.put("_sync_account", syncAccount);
+        if (syncAccount != null) {
+            values.put("_sync_account", syncAccount.mName);
+            values.put("_sync_account_type", syncAccount.mType);
+        }
         if (syncLocalId != null) values.put("_sync_local_id", syncLocalId);
         return values;
     }
@@ -380,6 +387,7 @@ public class AbstractTableMergerTest extends AndroidTestCase {
                     + "_sync_local_id INTEGER, "
                     + "_sync_dirty INTEGER NOT NULL DEFAULT 0, "
                     + "_sync_account TEXT, "
+                    + "_sync_account_type TEXT, "
                     + "_sync_mark INTEGER)");
 
             mDb.execSQL("CREATE TABLE deleted_items ("
@@ -388,6 +396,7 @@ public class AbstractTableMergerTest extends AndroidTestCase {
                     + "_sync_id TEXT, "
                     + "_sync_local_id INTEGER, "
                     + "_sync_account TEXT, "
+                    + "_sync_account_type TEXT, "
                     + "_sync_mark INTEGER)");
         }
 
@@ -501,7 +510,7 @@ public class AbstractTableMergerTest extends AndroidTestCase {
             throw new UnsupportedOperationException();
         }
 
-        public void onSyncStart(SyncContext context, String account) {
+        public void onSyncStart(SyncContext context, Account account) {
             throw new UnsupportedOperationException();
         }
 
@@ -509,7 +518,7 @@ public class AbstractTableMergerTest extends AndroidTestCase {
             throw new UnsupportedOperationException();
         }
 
-        public String getSyncingAccount() {
+        public Account getSyncingAccount() {
             throw new UnsupportedOperationException();
         }
 
@@ -544,24 +553,24 @@ public class AbstractTableMergerTest extends AndroidTestCase {
             throw new UnsupportedOperationException();
         }
 
-        protected void onAccountsChanged(String[] accountsArray) {
+        protected void onAccountsChanged(Account[] accountsArray) {
             throw new UnsupportedOperationException();
         }
 
-        protected void deleteRowsForRemovedAccounts(Map<String, Boolean> accounts, String table,
-                String accountColumnName) {
+        protected void deleteRowsForRemovedAccounts(Map<Account, Boolean> accounts, String table
+        ) {
             throw new UnsupportedOperationException();
         }
 
-        public void wipeAccount(String account) {
+        public void wipeAccount(Account account) {
             throw new UnsupportedOperationException();
         }
 
-        public byte[] readSyncDataBytes(String account) {
+        public byte[] readSyncDataBytes(Account account) {
             throw new UnsupportedOperationException();
         }
 
-        public void writeSyncDataBytes(String account, byte[] data) {
+        public void writeSyncDataBytes(Account account, byte[] data) {
             throw new UnsupportedOperationException();
         }
     }