change the sync framework and users to understand Account

This commit is contained in:
Fred Quintana
2009-04-23 13:36:27 -07:00
parent 843bbb8597
commit d9d2f1140b
25 changed files with 632 additions and 458 deletions

View File

@@ -13092,6 +13092,23 @@
<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"
@@ -13636,6 +13653,19 @@
<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"
@@ -13721,74 +13751,6 @@
</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"
@@ -14704,6 +14666,27 @@
</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"
@@ -99012,6 +98995,17 @@
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"
@@ -100954,6 +100948,17 @@
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"

View 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);
}
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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);
}

View 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);
}
}

View 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();

View 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());
}

View 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

View 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

View 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,44 +181,40 @@ 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();
mSyncStorageEngine.doDatabaseCleanup(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);
AccountManager.get(mContext).addOnAccountsUpdatedListener(SyncManager.this,
mSyncHandler, true /* updateImmediately */);
}
}
};
public void onAccountsUpdated(Account[] accounts) {
final boolean hadAccountsAlready = mAccounts != null;
mAccounts = accounts;
// 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() {
public void onReceive(Context context, Intent intent) {
@@ -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,

View 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});
}
}

View 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);
}

View 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);
}

View 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;

View 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});
}
/**

View 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;
@@ -74,6 +75,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);
@@ -839,6 +848,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>

View 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";
}
}

View 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;
@@ -50,6 +52,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() {

View File

@@ -28,6 +28,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";
}

View 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");

View 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=");
@@ -309,6 +312,8 @@ public class SubscribedFeedsProvider extends AbstractSyncableContentProvider {
SubscribedFeeds.Feeds._SYNC_ID, mValues);
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);
}
}

View 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

View 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;
}

View 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();

View 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();
}
}