From d9d2f1140b52fd0c014e9deac59f6000564b7e84 Mon Sep 17 00:00:00 2001 From: Fred Quintana Date: Thu, 23 Apr 2009 13:36:27 -0700 Subject: [PATCH] change the sync framework and users to understand Account --- api/current.xml | 141 +++++++++-------- core/java/android/accounts/AccountManager.java | 173 +++++++++++++++++++-- core/java/android/accounts/AccountMonitor.java | 120 -------------- ...istener.java => OnAccountsUpdatedListener.java} | 6 +- .../content/AbstractSyncableContentProvider.java | 88 ++++++----- core/java/android/content/AbstractTableMerger.java | 31 ++-- core/java/android/content/ContentResolver.java | 3 + core/java/android/content/ISyncAdapter.aidl | 3 +- core/java/android/content/SyncAdapter.java | 5 +- core/java/android/content/SyncManager.java | 131 ++++++++-------- .../content/SyncStateContentProviderHelper.java | 43 +++-- core/java/android/content/SyncStorageEngine.java | 90 +++++++---- .../android/content/SyncableContentProvider.java | 19 ++- .../android/content/TempProviderSyncAdapter.java | 15 +- core/java/android/provider/Calendar.java | 10 +- core/java/android/provider/Contacts.java | 17 +- core/java/android/provider/SubscribedFeeds.java | 31 ++-- core/java/android/provider/Sync.java | 36 +++-- core/java/android/provider/SyncConstValue.java | 11 ++ .../SubscribedFeedsIntentService.java | 47 +++--- .../subscribedfeeds/SubscribedFeedsProvider.java | 15 +- preloaded-classes | 1 - .../android/test/SyncBaseInstrumentation.java | 14 +- .../android/content/SyncStorageEngineTest.java | 3 +- .../android/content/AbstractTableMergerTest.java | 35 +++-- 25 files changed, 631 insertions(+), 457 deletions(-) delete mode 100644 core/java/android/accounts/AccountMonitor.java rename core/java/android/accounts/{AccountMonitorListener.java => OnAccountsUpdatedListener.java} (85%) diff --git a/api/current.xml b/api/current.xml index 9125da5406cc..caffcaf5aa3c 100644 --- a/api/current.xml +++ b/api/current.xml @@ -13092,6 +13092,23 @@ + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + void postToHandler(Handler handler, final Future1Callback callback, final Future1 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 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 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() { + public void run(Future1 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 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() { + public void run(Future1 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 index 38032c59ba66..000000000000 --- a/core/java/android/accounts/AccountMonitor.java +++ /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 mGetAccountsCallback = new Future1Callback() { - public void run(Future1 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(); - } - } -} diff --git a/core/java/android/accounts/AccountMonitorListener.java b/core/java/android/accounts/OnAccountsUpdatedListener.java similarity index 85% rename from core/java/android/accounts/AccountMonitorListener.java rename to core/java/android/accounts/OnAccountsUpdatedListener.java index d0bd9a93f3eb..bd249d0a3f9e 100644 --- a/core/java/android/accounts/AccountMonitorListener.java +++ b/core/java/android/accounts/OnAccountsUpdatedListener.java @@ -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); } diff --git a/core/java/android/content/AbstractSyncableContentProvider.java b/core/java/android/content/AbstractSyncableContentProvider.java index ce6501c9f147..edef332cd8c4 100644 --- a/core/java/android/content/AbstractSyncableContentProvider.java +++ b/core/java/android/content/AbstractSyncableContentProvider.java @@ -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 accounts = new HashMap(); - for (String account : accountsArray) { + protected void onAccountsChanged(Account[] accountsArray) { + Map accounts = Maps.newHashMap(); + for (Account account : accountsArray) { accounts.put(account, false); } - accounts.put(SyncConstValue.NON_SYNCABLE_ACCOUNT, false); SQLiteDatabase db = mOpenHelper.getWritableDatabase(); Map 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 accounts, - String table, String accountColumnName) { + protected void deleteRowsForRemovedAccounts(Map 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 tableMap = db.getSyncedTables(); ArrayList tables = new ArrayList(); @@ -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); } } diff --git a/core/java/android/content/AbstractTableMerger.java b/core/java/android/content/AbstractTableMerger.java index 55885134de84..8c9955a27a1f 100644 --- a/core/java/android/content/AbstractTableMerger.java +++ b/core/java/android/content/AbstractTableMerger.java @@ -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(); diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index 0a71d572932e..65772367ddeb 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -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 { *
  • Float
  • *
  • Double
  • *
  • String
  • + *
  • Account
  • *
  • null
  • * * @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()); } diff --git a/core/java/android/content/ISyncAdapter.aidl b/core/java/android/content/ISyncAdapter.aidl index 671188cd50c5..d228605b83a4 100644 --- a/core/java/android/content/ISyncAdapter.aidl +++ b/core/java/android/content/ISyncAdapter.aidl @@ -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 diff --git a/core/java/android/content/SyncAdapter.java b/core/java/android/content/SyncAdapter.java index 7826e5052ab7..3e916262f961 100644 --- a/core/java/android/content/SyncAdapter.java +++ b/core/java/android/content/SyncAdapter.java @@ -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 diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java index 01b07eb415df..4474c6277a88 100644 --- a/core/java/android/content/SyncManager.java +++ b/core/java/android/content/SyncManager.java @@ -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> entries = mOpsByKey.entrySet().iterator(); while (entries.hasNext()) { Map.Entry 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, diff --git a/core/java/android/content/SyncStateContentProviderHelper.java b/core/java/android/content/SyncStateContentProviderHelper.java index f503e6f3521d..dc728eccf3d4 100644 --- a/core/java/android/content/SyncStateContentProviderHelper.java +++ b/core/java/android/content/SyncStateContentProviderHelper.java @@ -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}); } } diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java index 282f6e71db70..2ad44d256725 100644 --- a/core/java/android/content/SyncStorageEngine.java +++ b/core/java/android/content/SyncStorageEngine.java @@ -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(); 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(); 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(); 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(); 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 currentAccounts = new HashSet(); - for (String account : accounts) currentAccounts.add(account); + protected void doDatabaseCleanup(Account[] accounts) { + HashSet 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); } diff --git a/core/java/android/content/SyncableContentProvider.java b/core/java/android/content/SyncableContentProvider.java index e0cd78643be6..93ebbeaac8fa 100644 --- a/core/java/android/content/SyncableContentProvider.java +++ b/core/java/android/content/SyncableContentProvider.java @@ -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 accounts, - String table, String accountColumnName); + protected abstract void deleteRowsForRemovedAccounts(Map 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); } diff --git a/core/java/android/content/TempProviderSyncAdapter.java b/core/java/android/content/TempProviderSyncAdapter.java index eb3a5da4a5b1..0cbe01e7e394 100644 --- a/core/java/android/content/TempProviderSyncAdapter.java +++ b/core/java/android/content/TempProviderSyncAdapter.java @@ -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; diff --git a/core/java/android/provider/Calendar.java b/core/java/android/provider/Calendar.java index 4a709f6c7517..3a221e436c01 100644 --- a/core/java/android/provider/Calendar.java +++ b/core/java/android/provider/Calendar.java @@ -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}); } /** diff --git a/core/java/android/provider/Contacts.java b/core/java/android/provider/Contacts.java index 064ed88edb89..a6450f3c5299 100644 --- a/core/java/android/provider/Contacts.java +++ b/core/java/android/provider/Contacts.java @@ -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. + *

    Type: TEXT

    + */ + public static final String _SYNC_ACCOUNT_TYPE = "_sync_account_type"; + + /** * The key of this setting. *

    Type: TEXT

    */ @@ -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. + *

    Type: TEXT

    + */ + public static final String GROUP_SYNC_ACCOUNT_TYPE = "group_sync_account_type"; + + /** * The row id of the person. *

    Type: TEXT

    */ diff --git a/core/java/android/provider/SubscribedFeeds.java b/core/java/android/provider/SubscribedFeeds.java index 4d430d5fd91a..f94b4427bd28 100644 --- a/core/java/android/provider/SubscribedFeeds.java +++ b/core/java/android/provider/SubscribedFeeds.java @@ -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 { *

    Type: TEXT

    */ public static final String _SYNC_ACCOUNT = SyncConstValue._SYNC_ACCOUNT; + + /** + * The account type. + *

    Type: TEXT

    + */ + 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"; } } diff --git a/core/java/android/provider/Sync.java b/core/java/android/provider/Sync.java index 628852f4c37c..c9bde0e4a1f9 100644 --- a/core/java/android/provider/Sync.java +++ b/core/java/android/provider/Sync.java @@ -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. + *

    Type: TEXT

    + */ + public static final String ACCOUNT_TYPE = "account_type"; + + /** * The content authority (contacts, calendar, etc.). *

    Type: TEXT

    */ @@ -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 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 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() { diff --git a/core/java/android/provider/SyncConstValue.java b/core/java/android/provider/SyncConstValue.java index 6eb4398444de..30966eb6f63d 100644 --- a/core/java/android/provider/SyncConstValue.java +++ b/core/java/android/provider/SyncConstValue.java @@ -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. + *

    Type: TEXT

    + */ + 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. *

    Type: TEXT

    */ @@ -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"; } diff --git a/packages/SubscribedFeedsProvider/src/com/android/providers/subscribedfeeds/SubscribedFeedsIntentService.java b/packages/SubscribedFeedsProvider/src/com/android/providers/subscribedfeeds/SubscribedFeedsIntentService.java index 8268fee0cb0f..de7b1d7b064b 100644 --- a/packages/SubscribedFeedsProvider/src/com/android/providers/subscribedfeeds/SubscribedFeedsIntentService.java +++ b/packages/SubscribedFeedsProvider/src/com/android/providers/subscribedfeeds/SubscribedFeedsIntentService.java @@ -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 accounts = new ArrayList(); + ArrayList 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"); diff --git a/packages/SubscribedFeedsProvider/src/com/android/providers/subscribedfeeds/SubscribedFeedsProvider.java b/packages/SubscribedFeedsProvider/src/com/android/providers/subscribedfeeds/SubscribedFeedsProvider.java index 9ecc3d61196b..d87f5e72ef14 100644 --- a/packages/SubscribedFeedsProvider/src/com/android/providers/subscribedfeeds/SubscribedFeedsProvider.java +++ b/packages/SubscribedFeedsProvider/src/com/android/providers/subscribedfeeds/SubscribedFeedsProvider.java @@ -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); } } diff --git a/preloaded-classes b/preloaded-classes index fc29906648cd..d1360eb87580 100644 --- a/preloaded-classes +++ b/preloaded-classes @@ -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 diff --git a/test-runner/android/test/SyncBaseInstrumentation.java b/test-runner/android/test/SyncBaseInstrumentation.java index c1d2507dce6c..0b4bfed3e9cc 100644 --- a/test-runner/android/test/SyncBaseInstrumentation.java +++ b/test-runner/android/test/SyncBaseInstrumentation.java @@ -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 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; } diff --git a/tests/CoreTests/android/content/SyncStorageEngineTest.java b/tests/CoreTests/android/content/SyncStorageEngineTest.java index 36805b15e864..27f1b8be63f2 100644 --- a/tests/CoreTests/android/content/SyncStorageEngineTest.java +++ b/tests/CoreTests/android/content/SyncStorageEngineTest.java @@ -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(); diff --git a/tests/FrameworkTest/tests/src/android/content/AbstractTableMergerTest.java b/tests/FrameworkTest/tests/src/android/content/AbstractTableMergerTest.java index aa3d186e3416..42c1e789b9c3 100644 --- a/tests/FrameworkTest/tests/src/android/content/AbstractTableMergerTest.java +++ b/tests/FrameworkTest/tests/src/android/content/AbstractTableMergerTest.java @@ -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 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 accounts, String table, - String accountColumnName) { + protected void deleteRowsForRemovedAccounts(Map 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(); } } -- 2.11.0