change the sync framework and users to understand Account
This commit is contained in:
141
api/current.xml
141
api/current.xml
@@ -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<java.lang.Void>"
|
||||
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=""group_sync_account_type""
|
||||
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=""_sync_account_type""
|
||||
static="true"
|
||||
final="true"
|
||||
deprecated="not deprecated"
|
||||
visibility="public"
|
||||
>
|
||||
</field>
|
||||
</interface>
|
||||
<class name="LiveFolders"
|
||||
extends="java.lang.Object"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2007 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.accounts;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.*;
|
||||
|
||||
/**
|
||||
* A helper class that calls back on the provided
|
||||
* AccountMonitorListener with the set of current accounts both when
|
||||
* it gets created and whenever the set changes. It does this by
|
||||
* binding to the AccountsService and registering to receive the
|
||||
* intent broadcast when the set of accounts is changed. The
|
||||
* connection to the accounts service is only made when it needs to
|
||||
* fetch the current list of accounts (that is, when the
|
||||
* AccountMonitor is first created, and when the intent is received).
|
||||
*/
|
||||
public class AccountMonitor extends BroadcastReceiver {
|
||||
private static final String TAG = "AccountMonitor";
|
||||
|
||||
private final Context mContext;
|
||||
private final AccountMonitorListener mListener;
|
||||
private boolean mClosed = false;
|
||||
|
||||
private volatile Looper mServiceLooper;
|
||||
private volatile NotifierHandler mServiceHandler;
|
||||
|
||||
/**
|
||||
* Initializes the AccountMonitor and initiates a bind to the
|
||||
* AccountsService to get the initial account list. For 1.0,
|
||||
* the "list" is always a single account.
|
||||
*
|
||||
* @param context the context we are running in
|
||||
* @param listener the user to notify when the account set changes
|
||||
*/
|
||||
public AccountMonitor(Context context, AccountMonitorListener listener) {
|
||||
if (listener == null) {
|
||||
throw new IllegalArgumentException("listener is null");
|
||||
}
|
||||
|
||||
mContext = context;
|
||||
mListener = listener;
|
||||
|
||||
// Register a broadcast receiver to monitor account changes
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(Constants.LOGIN_ACCOUNTS_CHANGED_ACTION);
|
||||
intentFilter.addAction(Intent.ACTION_DEVICE_STORAGE_OK); // To recover from disk-full.
|
||||
mContext.registerReceiver(this, intentFilter);
|
||||
|
||||
HandlerThread thread = new HandlerThread("AccountMonitorHandlerThread");
|
||||
thread.start();
|
||||
mServiceLooper = thread.getLooper();
|
||||
mServiceHandler = new NotifierHandler(mServiceLooper);
|
||||
|
||||
mServiceHandler.sendEmptyMessage(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
notifyListener();
|
||||
}
|
||||
|
||||
private Future1Callback<Account[]> mGetAccountsCallback = new Future1Callback<Account[]>() {
|
||||
public void run(Future1<Account[]> future) {
|
||||
try {
|
||||
Account[] accounts = future.getResult();
|
||||
String[] accountNames = new String[accounts.length];
|
||||
for (int i = 0; i < accounts.length; i++) {
|
||||
accountNames[i] = accounts[i].mName;
|
||||
}
|
||||
mListener.onAccountsUpdated(accountNames);
|
||||
} catch (OperationCanceledException e) {
|
||||
// the request was canceled
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private synchronized void notifyListener() {
|
||||
AccountManager.get(mContext).getAccounts(mGetAccountsCallback, null /* handler */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters the account receiver. Consecutive calls to this
|
||||
* method are harmless, but also do nothing. Once this call is
|
||||
* made no more notifications will occur.
|
||||
*/
|
||||
public synchronized void close() {
|
||||
if (!mClosed) {
|
||||
mContext.unregisterReceiver(this);
|
||||
mClosed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private final class NotifierHandler extends Handler {
|
||||
public NotifierHandler(Looper looper) {
|
||||
super(looper);
|
||||
}
|
||||
|
||||
public void handleMessage(Message msg) {
|
||||
notifyListener();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,11 +19,11 @@ package android.accounts;
|
||||
/**
|
||||
* An interface that contains the callback used by the AccountMonitor
|
||||
*/
|
||||
public interface AccountMonitorListener {
|
||||
public interface OnAccountsUpdatedListener {
|
||||
/**
|
||||
* This invoked when the AccountMonitor starts up and whenever the account
|
||||
* set changes.
|
||||
* @param currentAccounts the current accounts
|
||||
* @param accounts the current accounts
|
||||
*/
|
||||
void onAccountsUpdated(String[] currentAccounts);
|
||||
void onAccountsUpdated(Account[] accounts);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user