Reimplement EAS contacts sync to work w/ new system facilities

* Modify to work with ContactsProvider2
* Modify to work with system AccountManager
* Modify to work with system SyncManager (for triggering user-change syncs)
* Sync server->client for adds/deletes implemented (CP2 doesn't handle delete yet)
* Sync server->client changes handled efficiently (only write changes)
* Some fields still not handled
* Rewrote most of the CPO code to handle server->client changes
* Sync client->server works for supported fields
This commit is contained in:
Marc Blank 2009-07-27 10:24:58 -07:00
parent 9150b3005f
commit 948c36f47a
17 changed files with 1152 additions and 257 deletions

View File

@ -24,6 +24,7 @@
<uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" /> <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
<!-- For EAS purposes; could be removed when EAS has a permanent home --> <!-- For EAS purposes; could be removed when EAS has a permanent home -->
<uses-permission android:name="android.permission.WRITE_CONTACTS"/> <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
@ -174,6 +175,17 @@
> >
</service> </service>
<!--Required stanza to register the ContactsSyncAdapterService with SyncManager -->
<service
android:name="com.android.exchange.ContactsSyncAdapterService"
android:exported="true">
<intent-filter>
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data android:name="android.content.SyncAdapter"
android:resource="@xml/syncadapter_contacts" />
</service>
<!-- Add android:process=":remote" below to enable SyncManager as a separate process --> <!-- Add android:process=":remote" below to enable SyncManager as a separate process -->
<service <service
android:name="com.android.exchange.SyncManager" android:name="com.android.exchange.SyncManager"

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
/**
* Copyright (c) 2009, 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.
*/
-->
<!-- The attributes in this XML file provide configuration information -->
<!-- for the SyncAdapter. -->
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="com.android.contacts"
android:accountType="com.android.exchange"
/>

View File

@ -19,6 +19,7 @@ package com.android.email.activity.setup;
import com.android.email.Email; import com.android.email.Email;
import com.android.email.R; import com.android.email.R;
import com.android.email.mail.Store; import com.android.email.mail.Store;
import com.android.email.mail.store.ExchangeStore;
import com.android.email.provider.EmailContent; import com.android.email.provider.EmailContent;
import android.app.Activity; import android.app.Activity;
@ -120,6 +121,11 @@ public class AccountSetupOptions extends Activity implements OnClickListener {
mAccount.setSyncLookback(window); mAccount.setSyncLookback(window);
} }
mAccount.setDefaultAccount(mDefaultView.isChecked()); mAccount.setDefaultAccount(mDefaultView.isChecked());
// EAS needs a hook to store account information for use by AccountManager
if (!mAccount.isSaved() && mAccount.mHostAuthRecv != null
&& mAccount.mHostAuthRecv.mProtocol.equals("eas")) {
ExchangeStore.addSystemAccount(this, mAccount);
}
AccountSettingsUtils.commitSettings(this, mAccount); AccountSettingsUtils.commitSettings(this, mAccount);
Email.setServicesEnabled(this); Email.setServicesEnabled(this);
AccountSetupNames.actionSetNames(this, mAccount.mId); AccountSetupNames.actionSetNames(this, mAccount.mId);

View File

@ -26,6 +26,8 @@ import com.android.email.mail.MessageRetrievalListener;
import com.android.email.mail.MessagingException; import com.android.email.mail.MessagingException;
import com.android.email.mail.Store; import com.android.email.mail.Store;
import com.android.email.mail.StoreSynchronizer; import com.android.email.mail.StoreSynchronizer;
import com.android.email.provider.EmailContent.Account;
import com.android.email.service.EasAuthenticatorService;
import com.android.email.service.EmailServiceProxy; import com.android.email.service.EmailServiceProxy;
import com.android.exchange.Eas; import com.android.exchange.Eas;
import com.android.exchange.SyncManager; import com.android.exchange.SyncManager;
@ -115,6 +117,34 @@ public class ExchangeStore extends Store {
mTransport.checkSettings(mUri); mTransport.checkSettings(mUri);
} }
static public void addSystemAccount(Context context, Account acct) {
// This code was taken from sample code in AccountsTester
Bundle options = new Bundle();
options.putString(EasAuthenticatorService.OPTIONS_USERNAME, acct.mEmailAddress);
options.putString(EasAuthenticatorService.OPTIONS_PASSWORD, acct.mHostAuthRecv.mPassword);
Future2Callback callback = new Future2Callback() {
public void run(Future2 future) {
try {
Bundle bundle = future.getResult();
bundle.keySet();
Log.d(LOG_TAG, "account added: " + bundle);
} catch (OperationCanceledException e) {
Log.d(LOG_TAG, "addAccount was canceled");
} catch (IOException e) {
Log.d(LOG_TAG, "addAccount failed: " + e);
} catch (AuthenticatorException e) {
Log.d(LOG_TAG, "addAccount failed: " + e);
}
}
};
// Here's where we tell AccountManager about the new account. The addAccount
// method in AccountManager calls the addAccount method in our authenticator
// service (EasAuthenticatorService)
AccountManager.get(context).addAccount(Eas.ACCOUNT_MANAGER_TYPE, null, null,
options, null, callback, null);
}
@Override @Override
public Folder getFolder(String name) throws MessagingException { public Folder getFolder(String name) throws MessagingException {
synchronized (mFolders) { synchronized (mFolders) {
@ -287,32 +317,6 @@ public class ExchangeStore extends Store {
} else { } else {
throw new MessagingException(result); throw new MessagingException(result);
} }
} else {
// This code was taken from sample code in AccountsTester
Bundle options = new Bundle();
options.putString("username", mUsername);
options.putString("password", mPassword);
Future2Callback callback = new Future2Callback() {
public void run(Future2 future) {
try {
Bundle bundle = future.getResult();
bundle.keySet();
Log.d(TAG, "account added: " + bundle);
} catch (OperationCanceledException e) {
Log.d(TAG, "addAccount was canceled");
} catch (IOException e) {
Log.d(TAG, "addAccount failed: " + e);
} catch (AuthenticatorException e) {
Log.d(TAG, "addAccount failed: " + e);
}
}
};
// Here's where we tell AccountManager about the new account. The addAccount
// method in AccountManager calls the addAccount method in our authenticator
// service (EasAuthenticatorService)
AccountManager.get(mContext).addAccount(Eas.ACCOUNT_MANAGER_TYPE, null, null,
options, null, callback, null);
} }
} catch (RemoteException e) { } catch (RemoteException e) {
throw new MessagingException("Call to validate generated an exception", e); throw new MessagingException("Call to validate generated an exception", e);

View File

@ -25,10 +25,8 @@ import android.accounts.AccountManager;
import android.accounts.Constants; import android.accounts.Constants;
import android.accounts.NetworkErrorException; import android.accounts.NetworkErrorException;
import android.app.Service; import android.app.Service;
import android.content.Intent;
import android.content.Context; import android.content.Context;
import android.content.pm.PermissionInfo; import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
@ -38,6 +36,8 @@ import android.os.IBinder;
* password. We will need to implement confirmPassword, confirmCredentials, and updateCredentials. * password. We will need to implement confirmPassword, confirmCredentials, and updateCredentials.
*/ */
public class EasAuthenticatorService extends Service { public class EasAuthenticatorService extends Service {
public static final String OPTIONS_USERNAME = "username";
public static final String OPTIONS_PASSWORD = "password";
class EasAuthenticator extends AbstractAccountAuthenticator { class EasAuthenticator extends AbstractAccountAuthenticator {
public EasAuthenticator(Context context) { public EasAuthenticator(Context context) {
@ -50,8 +50,8 @@ public class EasAuthenticatorService extends Service {
throws NetworkErrorException { throws NetworkErrorException {
// The Bundle we are passed has username and password set // The Bundle we are passed has username and password set
AccountManager.get(EasAuthenticatorService.this).blockingAddAccountExplicitly( AccountManager.get(EasAuthenticatorService.this).blockingAddAccountExplicitly(
new Account(options.getString("username"), Eas.ACCOUNT_MANAGER_TYPE), new Account(options.getString(OPTIONS_USERNAME), Eas.ACCOUNT_MANAGER_TYPE),
options.getString("password"), null); options.getString(OPTIONS_PASSWORD), null);
Bundle b = new Bundle(); Bundle b = new Bundle();
b.putString(Constants.ACCOUNT_NAME_KEY, options.getString("username")); b.putString(Constants.ACCOUNT_NAME_KEY, options.getString("username"));
b.putString(Constants.ACCOUNT_TYPE_KEY, Eas.ACCOUNT_MANAGER_TYPE); b.putString(Constants.ACCOUNT_TYPE_KEY, Eas.ACCOUNT_MANAGER_TYPE);
@ -84,7 +84,7 @@ public class EasAuthenticatorService extends Service {
@Override @Override
public String getAuthTokenLabel(String authTokenType) { public String getAuthTokenLabel(String authTokenType) {
// null means we don't have compartmentalized authtoken types // null means we don't have compartmentalized authtoken types
return null; return null;
} }
@ -107,7 +107,7 @@ public class EasAuthenticatorService extends Service {
public IBinder onBind(Intent intent) { public IBinder onBind(Intent intent) {
// TODO Replace this with an appropriate constant in AccountManager, when it's created // TODO Replace this with an appropriate constant in AccountManager, when it's created
String authenticatorIntent = "android.accounts.AccountAuthenticator"; String authenticatorIntent = "android.accounts.AccountAuthenticator";
if (authenticatorIntent.equals(intent.getAction())) { if (authenticatorIntent.equals(intent.getAction())) {
return new EasAuthenticator(this).getIAccountAuthenticator().asBinder(); return new EasAuthenticator(this).getIAccountAuthenticator().asBinder();
} else { } else {

View File

@ -0,0 +1,105 @@
/*
* Copyright (C) 2009 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 com.android.exchange;
import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent.AccountColumns;
import com.android.email.provider.EmailContent.Mailbox;
import com.android.email.provider.EmailContent.MailboxColumns;
import android.accounts.Account;
import android.accounts.OperationCanceledException;
import android.app.Service;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Intent;
import android.content.SyncResult;
import android.database.Cursor;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;
public class ContactsSyncAdapterService extends Service {
private final String TAG = "EAS ContactsSyncAdapterService";
private final SyncAdapterImpl mSyncAdapter;
private static final String[] ID_PROJECTION = new String[] {EmailContent.RECORD_ID};
private static final String ACCOUNT_AND_TYPE_CONTACTS =
MailboxColumns.ACCOUNT_KEY + "=? AND " + MailboxColumns.TYPE + '=' + Mailbox.TYPE_CONTACTS;
public ContactsSyncAdapterService() {
super();
mSyncAdapter = new SyncAdapterImpl();
}
private class SyncAdapterImpl extends AbstractThreadedSyncAdapter {
public SyncAdapterImpl() {
super(ContactsSyncAdapterService.this);
}
@Override
public void performSync(Account account, Bundle extras,
String authority, ContentProviderClient provider, SyncResult syncResult) {
try {
ContactsSyncAdapterService.this.performSync(account, extras,
authority, provider, syncResult);
} catch (OperationCanceledException e) {
}
}
}
@Override
public IBinder onBind(Intent intent) {
return mSyncAdapter.getISyncAdapter().asBinder();
}
/**
* Partial integration with system SyncManager; we tell our EAS SyncManager to start a contacts
* sync when we get the signal from the system SyncManager.
* The missing piece at this point is integration with the push/ping mechanism in EAS; this will
* be put in place at a later time.
*/
private void performSync(Account account, Bundle extras, String authority,
ContentProviderClient provider, SyncResult syncResult)
throws OperationCanceledException {
ContentResolver cr = getContentResolver();
// Find the (EmailProvider) account associated with this email address
Cursor accountCursor =
cr.query(com.android.email.provider.EmailContent.Account.CONTENT_URI, ID_PROJECTION,
AccountColumns.EMAIL_ADDRESS + "=?", new String[] {account.mName}, null);
try {
if (accountCursor.moveToFirst()) {
long accountId = accountCursor.getLong(0);
// Now, find the contacts mailbox associated with the account
Cursor mailboxCursor = cr.query(Mailbox.CONTENT_URI, ID_PROJECTION,
ACCOUNT_AND_TYPE_CONTACTS, new String[] {Long.toString(accountId)}, null);
try {
if (mailboxCursor.moveToFirst()) {
Log.i(TAG, "Contact sync requested for " + account.mName);
// Ask for a sync from our sync manager
SyncManager.serviceRequest(mailboxCursor.getLong(0));
}
} finally {
mailboxCursor.close();
}
}
} finally {
accountCursor.close();
}
}
}

View File

@ -347,6 +347,7 @@ public class EasSyncService extends InteractiveSyncService {
return setupEASCommand(method, cmd, null); return setupEASCommand(method, cmd, null);
} }
@SuppressWarnings("deprecation")
private String makeUriString(String cmd, String extra) { private String makeUriString(String cmd, String extra) {
// Cache the authentication string and the command string // Cache the authentication string and the command string
if (mDeviceId == null) if (mDeviceId == null)
@ -691,7 +692,7 @@ public class EasSyncService extends InteractiveSyncService {
BufferedReader rdr = null; BufferedReader rdr = null;
String id; String id;
if (f.exists() && f.canRead()) { if (f.exists() && f.canRead()) {
rdr = new BufferedReader(new FileReader(f)); rdr = new BufferedReader(new FileReader(f), 128);
id = rdr.readLine(); id = rdr.readLine();
rdr.close(); rdr.close();
return id; return id;
@ -853,7 +854,9 @@ public class EasSyncService extends InteractiveSyncService {
mAccount = Account.restoreAccountWithId(mContext, mAccount.mId); mAccount = Account.restoreAccountWithId(mContext, mAccount.mId);
mMailbox = Mailbox.restoreMailboxWithId(mContext, mMailbox.mId); mMailbox = Mailbox.restoreMailboxWithId(mContext, mMailbox.mId);
try { try {
if (mMailbox.mServerId.equals(Eas.ACCOUNT_MAILBOX)) { if (mMailbox == null || mAccount == null) {
return;
} else if (mMailbox.mServerId.equals(Eas.ACCOUNT_MAILBOX)) {
runMain(); runMain();
} else { } else {
EasSyncAdapter target; EasSyncAdapter target;
@ -861,9 +864,9 @@ public class EasSyncService extends InteractiveSyncService {
mProtocolVersion = mAccount.mProtocolVersion; mProtocolVersion = mAccount.mProtocolVersion;
mProtocolVersionDouble = Double.parseDouble(mProtocolVersion); mProtocolVersionDouble = Double.parseDouble(mProtocolVersion);
if (mMailbox.mType == Mailbox.TYPE_CONTACTS) if (mMailbox.mType == Mailbox.TYPE_CONTACTS)
target = new EasContactsSyncAdapter(mMailbox); target = new EasContactsSyncAdapter(mMailbox, this);
else { else {
target = new EasEmailSyncAdapter(mMailbox); target = new EasEmailSyncAdapter(mMailbox, this);
} }
// We loop here because someone might have put a request in while we were syncing // We loop here because someone might have put a request in while we were syncing
// and we've missed that opportunity... // and we've missed that opportunity...

View File

@ -31,7 +31,7 @@ import android.util.Log;
import java.util.ArrayList; import java.util.ArrayList;
/** /**
* UserSyncAlarmReceiver (USAR) is used by the SyncManager to start up-syncs of user-modified data * EmailSyncAlarmReceiver (USAR) is used by the SyncManager to start up-syncs of user-modified data
* back to the Exchange server. * back to the Exchange server.
* *
* Here's how this works for Email, for example: * Here's how this works for Email, for example:
@ -40,15 +40,15 @@ import java.util.ArrayList;
* 2) SyncManager, which has a ContentObserver watching the Message class, is alerted to a change * 2) SyncManager, which has a ContentObserver watching the Message class, is alerted to a change
* 3) SyncManager sets an alarm (to be received by USAR) for a few seconds in the * 3) SyncManager sets an alarm (to be received by USAR) for a few seconds in the
* future (currently 15), the delay preventing excess syncing (think of it as a debounce mechanism). * future (currently 15), the delay preventing excess syncing (think of it as a debounce mechanism).
* 4) USAR Receiver's onReceive method is called * 4) ESAR Receiver's onReceive method is called
* 5) USAR goes through all change and deletion records and compiles a list of mailboxes which have * 5) ESAR goes through all change and deletion records and compiles a list of mailboxes which have
* changes to be uploaded. * changes to be uploaded.
* 6) USAR calls SyncManager to start syncs of those mailboxes * 6) ESAR calls SyncManager to start syncs of those mailboxes
* *
*/ */
public class UserSyncAlarmReceiver extends BroadcastReceiver { public class EmailSyncAlarmReceiver extends BroadcastReceiver {
final String[] MAILBOX_DATA_PROJECTION = {MessageColumns.MAILBOX_KEY, SyncColumns.DATA}; final String[] MAILBOX_DATA_PROJECTION = {MessageColumns.MAILBOX_KEY, SyncColumns.DATA};
private static String TAG = "UserSyncAlarm"; private static String TAG = "EmailSyncAlarm";
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {

View File

@ -59,7 +59,7 @@ import java.util.List;
/** /**
* The SyncManager handles all aspects of starting, maintaining, and stopping the various sync * The SyncManager handles all aspects of starting, maintaining, and stopping the various sync
* adapters used by Exchange. However, it is capable of handing any kind of email sync, and it * adapters used by Exchange. However, it is capable of handing any kind of email sync, and it
* would be appropriate to use for IMAP push, when that functionality is added to the Email * would be appropriate to use for IMAP push, when that functionality is added to the Email
* application. * application.
* *
@ -91,17 +91,17 @@ public class SyncManager extends Service implements Runnable {
MessageObserver mMessageObserver; MessageObserver mMessageObserver;
String mNextWaitReason; String mNextWaitReason;
IEmailServiceCallback mCallback; IEmailServiceCallback mCallback;
RemoteCallbackList<IEmailServiceCallback> mCallbackList = RemoteCallbackList<IEmailServiceCallback> mCallbackList =
new RemoteCallbackList<IEmailServiceCallback>(); new RemoteCallbackList<IEmailServiceCallback>();
static private HashMap<Long, Boolean> mWakeLocks = new HashMap<Long, Boolean>(); static private HashMap<Long, Boolean> mWakeLocks = new HashMap<Long, Boolean>();
static private HashMap<Long, PendingIntent> mPendingIntents = static private HashMap<Long, PendingIntent> mPendingIntents =
new HashMap<Long, PendingIntent>(); new HashMap<Long, PendingIntent>();
static private WakeLock mWakeLock = null; static private WakeLock mWakeLock = null;
/** /**
* Create the binder for EmailService implementation here. These are the calls that are * Create the binder for EmailService implementation here. These are the calls that are
* defined in AbstractSyncService. Only validate is now implemented; loadAttachment currently * defined in AbstractSyncService. Only validate is now implemented; loadAttachment currently
* spins its wheels counting up to 100%. * spins its wheels counting up to 100%.
*/ */
@ -183,24 +183,24 @@ public class SyncManager extends Service implements Runnable {
} }
}; };
class AccountList extends ArrayList<Account> {
private static final long serialVersionUID = 1L;
public boolean contains(long id) {
for (Account account: this) {
if (account.mId == id) {
return true;
}
}
return false;
}
}
class AccountObserver extends ContentObserver { class AccountObserver extends ContentObserver {
// mAccounts keeps track of Accounts that we care about (EAS for now) // mAccounts keeps track of Accounts that we care about (EAS for now)
AccountList mAccounts = new AccountList(); AccountList mAccounts = new AccountList();
class AccountList extends ArrayList<Account> {
private static final long serialVersionUID = 1L;
public boolean contains(long id) {
for (Account account: this) {
if (account.mId == id) {
return true;
}
}
return false;
}
}
public AccountObserver(Handler handler) { public AccountObserver(Handler handler) {
super(handler); super(handler);
Context context = getContext(); Context context = getContext();
@ -237,6 +237,7 @@ public class SyncManager extends Service implements Runnable {
return false; return false;
} }
@Override
public void onChange(boolean selfChange) { public void onChange(boolean selfChange) {
// A change to the list requires us to scan for deletions (to stop running syncs) // A change to the list requires us to scan for deletions (to stop running syncs)
// At startup, we want to see what accounts exist and cache them // At startup, we want to see what accounts exist and cache them
@ -328,6 +329,7 @@ public class SyncManager extends Service implements Runnable {
super(handler); super(handler);
} }
@Override
public void onChange(boolean selfChange) { public void onChange(boolean selfChange) {
// See if there's anything to do... // See if there's anything to do...
kick(); kick();
@ -337,7 +339,7 @@ public class SyncManager extends Service implements Runnable {
class SyncedMessageObserver extends ContentObserver { class SyncedMessageObserver extends ContentObserver {
long maxChangedId = 0; long maxChangedId = 0;
long maxDeletedId = 0; long maxDeletedId = 0;
Intent syncAlarmIntent = new Intent(INSTANCE, UserSyncAlarmReceiver.class); Intent syncAlarmIntent = new Intent(INSTANCE, EmailSyncAlarmReceiver.class);
PendingIntent syncAlarmPendingIntent = PendingIntent syncAlarmPendingIntent =
PendingIntent.getBroadcast(INSTANCE, 0, syncAlarmIntent, 0); PendingIntent.getBroadcast(INSTANCE, 0, syncAlarmIntent, 0);
AlarmManager alarmManager = (AlarmManager)INSTANCE.getSystemService(Context.ALARM_SERVICE); AlarmManager alarmManager = (AlarmManager)INSTANCE.getSystemService(Context.ALARM_SERVICE);
@ -347,6 +349,7 @@ public class SyncManager extends Service implements Runnable {
super(handler); super(handler);
} }
@Override
public void onChange(boolean selfChange) { public void onChange(boolean selfChange) {
INSTANCE.log("SyncedMessage changed: (re)setting alarm for 10s"); INSTANCE.log("SyncedMessage changed: (re)setting alarm for 10s");
alarmManager.set(AlarmManager.RTC_WAKEUP, alarmManager.set(AlarmManager.RTC_WAKEUP,
@ -360,6 +363,7 @@ public class SyncManager extends Service implements Runnable {
super(handler); super(handler);
} }
@Override
public void onChange(boolean selfChange) { public void onChange(boolean selfChange) {
INSTANCE.log("MessageObserver"); INSTANCE.log("MessageObserver");
// A rather blunt instrument here. But we don't have information about the URI that // A rather blunt instrument here. But we don't have information about the URI that
@ -374,7 +378,15 @@ public class SyncManager extends Service implements Runnable {
} }
return null; return null;
} }
static public AccountList getAccountList() {
if (INSTANCE != null) {
return INSTANCE.mAccountObserver.mAccounts;
} else {
return null;
}
}
public class SyncStatus { public class SyncStatus {
static public final int NOT_RUNNING = 0; static public final int NOT_RUNNING = 0;
static public final int DIED = 1; static public final int DIED = 1;
@ -651,13 +663,6 @@ public class SyncManager extends Service implements Runnable {
public void run() { public void run() {
mStop = false; mStop = false;
// if (Debug.isDebuggerConnected()) {
// try {
// Thread.sleep(10000L);
// } catch (InterruptedException e) {
// }
// }
runAwake(-1); runAwake(-1);
ContentResolver resolver = getContentResolver(); ContentResolver resolver = getContentResolver();
@ -668,7 +673,7 @@ public class SyncManager extends Service implements Runnable {
ConnectivityReceiver cr = new ConnectivityReceiver(); ConnectivityReceiver cr = new ConnectivityReceiver();
registerReceiver(cr, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); registerReceiver(cr, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
ConnectivityManager cm = ConnectivityManager cm =
(ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
try { try {
@ -721,6 +726,22 @@ public class SyncManager extends Service implements Runnable {
} }
long checkMailboxes () { long checkMailboxes () {
// First, see if any running mailboxes have been deleted
ArrayList<Long> deadMailboxes = new ArrayList<Long>();
synchronized (mSyncToken) {
for (long mailboxId: mServiceMap.keySet()) {
Mailbox m = Mailbox.restoreMailboxWithId(INSTANCE, mailboxId);
if (m == null) {
deadMailboxes.add(mailboxId);
log("Stopping sync for mailbox " + mailboxId + "; record not found.");
}
}
}
// If so, stop them
for (Long mailboxId: deadMailboxes) {
stopManualSync(mailboxId);
}
long nextWait = 10*MINS; long nextWait = 10*MINS;
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
// Start up threads that need it... // Start up threads that need it...
@ -800,7 +821,7 @@ public class SyncManager extends Service implements Runnable {
} }
return nextWait; return nextWait;
} }
static public void serviceRequest(Mailbox m) { static public void serviceRequest(Mailbox m) {
serviceRequest(m.mId, 5*SECS); serviceRequest(m.mId, 5*SECS);
} }
@ -881,7 +902,7 @@ public class SyncManager extends Service implements Runnable {
/** /**
* Determine whether a given Mailbox can be synced, i.e. is not already syncing and is not in * Determine whether a given Mailbox can be synced, i.e. is not already syncing and is not in
* an error state * an error state
* *
* @param mailboxId * @param mailboxId
* @return whether or not the Mailbox is available for syncing (i.e. is a valid push target) * @return whether or not the Mailbox is available for syncing (i.e. is a valid push target)
*/ */
@ -896,7 +917,7 @@ public class SyncManager extends Service implements Runnable {
} }
return true; return true;
} }
static public int getSyncStatus(long mailboxId) { static public int getSyncStatus(long mailboxId) {
synchronized (mSyncToken) { synchronized (mSyncToken) {
if (INSTANCE == null || INSTANCE.mServiceMap == null) { if (INSTANCE == null || INSTANCE.mServiceMap == null) {
@ -984,30 +1005,32 @@ public class SyncManager extends Service implements Runnable {
* @param svc the service that is finished * @param svc the service that is finished
*/ */
static public void done(AbstractSyncService svc) { static public void done(AbstractSyncService svc) {
long mailboxId = svc.mMailboxId; synchronized(mSyncToken) {
HashMap<Long, SyncError> errorMap = INSTANCE.mSyncErrorMap; long mailboxId = svc.mMailboxId;
SyncError syncError = errorMap.get(mailboxId); HashMap<Long, SyncError> errorMap = INSTANCE.mSyncErrorMap;
INSTANCE.mServiceMap.remove(mailboxId); SyncError syncError = errorMap.get(mailboxId);
int exitStatus = svc.mExitStatus; INSTANCE.mServiceMap.remove(mailboxId);
switch (exitStatus) { int exitStatus = svc.mExitStatus;
case AbstractSyncService.EXIT_DONE: switch (exitStatus) {
if (!svc.mPartRequests.isEmpty()) { case AbstractSyncService.EXIT_DONE:
// TODO Handle this case if (!svc.mPartRequests.isEmpty()) {
} // TODO Handle this case
errorMap.remove(mailboxId); }
break; errorMap.remove(mailboxId);
case AbstractSyncService.EXIT_IO_ERROR: break;
if (syncError != null) { case AbstractSyncService.EXIT_IO_ERROR:
syncError.escalate(); if (syncError != null) {
} else { syncError.escalate();
errorMap.put(mailboxId, INSTANCE.new SyncError(exitStatus, false)); } else {
} errorMap.put(mailboxId, INSTANCE.new SyncError(exitStatus, false));
kick(); }
break; kick();
case AbstractSyncService.EXIT_LOGIN_FAILURE: break;
case AbstractSyncService.EXIT_EXCEPTION: case AbstractSyncService.EXIT_LOGIN_FAILURE:
errorMap.put(mailboxId, INSTANCE.new SyncError(exitStatus, true)); case AbstractSyncService.EXIT_EXCEPTION:
break; errorMap.put(mailboxId, INSTANCE.new SyncError(exitStatus, true));
break;
}
} }
} }
@ -1034,6 +1057,6 @@ public class SyncManager extends Service implements Runnable {
if (INSTANCE == null) { if (INSTANCE == null) {
return null; return null;
} }
return (Context)INSTANCE; return INSTANCE;
} }
} }

View File

@ -29,8 +29,8 @@ import java.io.IOException;
*/ */
public class EasCalendarSyncAdapter extends EasSyncAdapter { public class EasCalendarSyncAdapter extends EasSyncAdapter {
public EasCalendarSyncAdapter(Mailbox mailbox) { public EasCalendarSyncAdapter(Mailbox mailbox, EasSyncService service) {
super(mailbox); super(mailbox, service);
} }
@Override @Override

File diff suppressed because it is too large Load Diff

View File

@ -63,8 +63,8 @@ public class EasEmailSyncAdapter extends EasSyncAdapter {
ArrayList<Long> mDeletedIdList = new ArrayList<Long>(); ArrayList<Long> mDeletedIdList = new ArrayList<Long>();
ArrayList<Long> mUpdatedIdList = new ArrayList<Long>(); ArrayList<Long> mUpdatedIdList = new ArrayList<Long>();
public EasEmailSyncAdapter(Mailbox mailbox) { public EasEmailSyncAdapter(Mailbox mailbox, EasSyncService service) {
super(mailbox); super(mailbox, service);
} }
@Override @Override
@ -72,10 +72,10 @@ public class EasEmailSyncAdapter extends EasSyncAdapter {
EasEmailSyncParser p = new EasEmailSyncParser(is, service); EasEmailSyncParser p = new EasEmailSyncParser(is, service);
return p.parse(); return p.parse();
} }
public class EasEmailSyncParser extends EasContentParser { public class EasEmailSyncParser extends EasContentParser {
private static final String WHERE_SERVER_ID_AND_MAILBOX_KEY = private static final String WHERE_SERVER_ID_AND_MAILBOX_KEY =
SyncColumns.SERVER_ID + "=? and " + MessageColumns.MAILBOX_KEY + "=?"; SyncColumns.SERVER_ID + "=? and " + MessageColumns.MAILBOX_KEY + "=?";
private String mMailboxIdAsString; private String mMailboxIdAsString;
@ -88,6 +88,7 @@ public class EasEmailSyncAdapter extends EasSyncAdapter {
} }
} }
@Override
public void wipe() { public void wipe() {
mContentResolver.delete(Message.CONTENT_URI, mContentResolver.delete(Message.CONTENT_URI,
Message.MAILBOX_KEY + "=" + mMailbox.mId, null); Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
@ -174,7 +175,7 @@ public class EasEmailSyncAdapter extends EasSyncAdapter {
while (nextTag(EasTags.SYNC_ADD) != END) { while (nextTag(EasTags.SYNC_ADD) != END) {
switch (tag) { switch (tag) {
case EasTags.SYNC_SERVER_ID: case EasTags.SYNC_SERVER_ID:
msg.mServerId = getValue(); msg.mServerId = getValue();
break; break;
case EasTags.SYNC_APPLICATION_DATA: case EasTags.SYNC_APPLICATION_DATA:
@ -389,6 +390,7 @@ public class EasEmailSyncAdapter extends EasSyncAdapter {
/* (non-Javadoc) /* (non-Javadoc)
* @see com.android.exchange.adapter.EasContentParser#commandsParser() * @see com.android.exchange.adapter.EasContentParser#commandsParser()
*/ */
@Override
public void commandsParser() throws IOException { public void commandsParser() throws IOException {
ArrayList<Message> newEmails = new ArrayList<Message>(); ArrayList<Message> newEmails = new ArrayList<Message>();
ArrayList<Long> deletedEmails = new ArrayList<Long>(); ArrayList<Long> deletedEmails = new ArrayList<Long>();
@ -436,7 +438,7 @@ public class EasEmailSyncAdapter extends EasSyncAdapter {
mMailbox.toContentValues()).build()); mMailbox.toContentValues()).build());
addCleanupOps(ops); addCleanupOps(ops);
try { try {
mService.mContext.getContentResolver() mService.mContext.getContentResolver()
.applyBatch(EmailProvider.EMAIL_AUTHORITY, ops); .applyBatch(EmailProvider.EMAIL_AUTHORITY, ops);

View File

@ -51,7 +51,7 @@ import java.util.List;
public class EasFolderSyncParser extends EasParser { public class EasFolderSyncParser extends EasParser {
private static boolean DEBUG_LOGGING = false; private static boolean DEBUG_LOGGING = true;
public static final String TAG = "FolderSyncParser"; public static final String TAG = "FolderSyncParser";

View File

@ -29,6 +29,7 @@ import java.io.IOException;
*/ */
public abstract class EasSyncAdapter { public abstract class EasSyncAdapter {
public Mailbox mMailbox; public Mailbox mMailbox;
public EasSyncService mService;
// Create the data for local changes that need to be sent up to the server // Create the data for local changes that need to be sent up to the server
public abstract boolean sendLocalChanges(EasSerializer s, EasSyncService service) public abstract boolean sendLocalChanges(EasSerializer s, EasSyncService service)
@ -41,8 +42,9 @@ public abstract class EasSyncAdapter {
public abstract String getCollectionName(); public abstract String getCollectionName();
public abstract void cleanup(EasSyncService service); public abstract void cleanup(EasSyncService service);
public EasSyncAdapter(Mailbox mailbox) { public EasSyncAdapter(Mailbox mailbox, EasSyncService service) {
mMailbox = mailbox; mMailbox = mailbox;
mService = service;
} }
} }

View File

@ -329,8 +329,9 @@ public class EasTags {
}, },
{ {
// 0x01 Contacts // 0x01 Contacts
"Anniversary", "AssistantName", "AssistantTelephoneNumber", "Birthday", "Body", "Anniversary", "AssistantName", "AssistantTelephoneNumber", "Birthday", "ContactsBody",
"BodySize", "BodyTruncated", "Business2TelephoneNumber", "BusinessAddressCity", "ContactsBodySize", "ContactsBodyTruncated", "Business2TelephoneNumber",
"BusinessAddressCity",
"BusinessAddressCountry", "BusinessAddressPostalCode", "BusinessAddressState", "BusinessAddressCountry", "BusinessAddressPostalCode", "BusinessAddressState",
"BusinessAddressStreet", "BusinessFaxNumber", "BusinessTelephoneNumber", "BusinessAddressStreet", "BusinessFaxNumber", "BusinessTelephoneNumber",
"CarTelephoneNumber", "ContactsCategories", "ContactsCategory", "Children", "Child", "CarTelephoneNumber", "ContactsCategories", "ContactsCategory", "Children", "Child",
@ -338,8 +339,8 @@ public class EasTags {
"FileAs", "FirstName", "Home2TelephoneNumber", "HomeAddressCity", "HomeAddressCountry", "FileAs", "FirstName", "Home2TelephoneNumber", "HomeAddressCity", "HomeAddressCountry",
"HomeAddressPostalCode", "HomeAddressState", "HomeAddressStreet", "HomeFaxNumber", "HomeAddressPostalCode", "HomeAddressState", "HomeAddressStreet", "HomeFaxNumber",
"HomeTelephoneNumber", "JobTitle", "LastName", "MiddleName", "MobileTelephoneNumber", "HomeTelephoneNumber", "JobTitle", "LastName", "MiddleName", "MobileTelephoneNumber",
"OfficeLocation", "OfficeAddressCity", "OfficeAddressCountry", "OfficeLocation", "OtherAddressCity", "OtherAddressCountry",
"OfficeAddressPostalCode", "OfficeAddressState", "OfficeAddressStreet", "PagerNumber", "OtherAddressPostalCode", "OtherAddressState", "OtherAddressStreet", "PagerNumber",
"RadioTelephoneNumber", "Spouse", "Suffix", "Title", "Webpage", "YomiCompanyName", "RadioTelephoneNumber", "Spouse", "Suffix", "Title", "Webpage", "YomiCompanyName",
"YomiFirstName", "YomiLastName", "CompressedRTF", "Picture" "YomiFirstName", "YomiLastName", "CompressedRTF", "Picture"
}, },
@ -354,7 +355,7 @@ public class EasTags {
"Recurrence_Occurrences", "Recurrence_Interval", "Recurrence_DayOfWeek", "Recurrence_Occurrences", "Recurrence_Interval", "Recurrence_DayOfWeek",
"Recurrence_DayOfMonth", "Recurrence_WeekOfMonth", "Recurrence_MonthOfYear", "Recurrence_DayOfMonth", "Recurrence_WeekOfMonth", "Recurrence_MonthOfYear",
"StartTime", "Sensitivity", "TimeZone", "GlobalObjId", "ThreadTopic", "MIMEData", "StartTime", "Sensitivity", "TimeZone", "GlobalObjId", "ThreadTopic", "MIMEData",
"MIMETruncated", "MIMESize", "InternetCPID", "Flag", "FlagStatus", "ContentClass", "MIMETruncated", "MIMESize", "InternetCPID", "Flag", "FlagStatus", "EmailContentClass",
"FlagType", "CompleteTime" "FlagType", "CompleteTime"
}, },
{ {
@ -375,7 +376,7 @@ public class EasTags {
}, },
{ {
// 0x05 Move // 0x05 Move
"MoveItems", "Move", "SrcMsgId", "SrcFldId", "DstFldId", "Response", "Status", "MoveItems", "Move", "SrcMsgId", "SrcFldId", "DstFldId", "MoveResponse", "MoveStatus",
"DstMsgId" "DstMsgId"
}, },
{ {
@ -384,9 +385,9 @@ public class EasTags {
{ {
// 0x07 FolderHierarchy // 0x07 FolderHierarchy
"Folders", "Folder", "FolderDisplayName", "FolderServerId", "FolderParentId", "Type", "Folders", "Folder", "FolderDisplayName", "FolderServerId", "FolderParentId", "Type",
"Response", "Status", "ContentClass", "Changes", "FolderAdd", "FolderDelete", "FolderResponse", "FolderStatus", "FolderContentClass", "Changes", "FolderAdd",
"FolderUpdate", "FolderSyncKey", "FolderCreate", "FolderDelete", "FolderUpdate", "FolderDelete", "FolderUpdate", "FolderSyncKey", "FolderFolderCreate",
"FolderSync", "Count", "Version" "FolderFolderDelete", "FolderFolderUpdate", "FolderSync", "Count", "FolderVersion"
}, },
{ {
// 0x08 MeetingResponse // 0x08 MeetingResponse
@ -407,12 +408,12 @@ public class EasTags {
}, },
{ {
// 0x0D Ping // 0x0D Ping
"Ping", "AutdState", "Status", "HeartbeatInterval", "PingFolders", "PingFolder", "Ping", "AutdState", "PingStatus", "HeartbeatInterval", "PingFolders", "PingFolder",
"PingId", "PingClass", "MaxFolders" "PingId", "PingClass", "MaxFolders"
}, },
{ {
// 0x0E Provision // 0x0E Provision
"Provision", "Policies", "Policy", "PolicyType", "PolicyKey", "Data", "Status", "Provision", "Policies", "Policy", "PolicyType", "PolicyKey", "Data", "ProvisionStatus",
"RemoteWipe", "EASProvidionDoc", "DevicePasswordEnabled", "RemoteWipe", "EASProvidionDoc", "DevicePasswordEnabled",
"AlphanumericDevicePasswordRequired", "AlphanumericDevicePasswordRequired",
"DeviceEncryptionEnabled", "-unused-", "AttachmentsEnabled", "MinDevicePasswordLength", "DeviceEncryptionEnabled", "-unused-", "AttachmentsEnabled", "MinDevicePasswordLength",
@ -436,8 +437,8 @@ public class EasTags {
}, },
{ {
// 0x10 Gal // 0x10 Gal
"DisplayName", "Phone", "Office", "Title", "Company", "Alias", "FirstName", "LastName", "GalDisplayName", "GalPhone", "GalOffice", "GalTitle", "GalCompany", "GalAlias",
"HomePhone", "MobilePhone", "EmailAddress" "GalFirstName", "GalLastName", "GalHomePhone", "GalMobilePhone", "GalEmailAddress"
}, },
{ {
// 0x11 AirSyncBase // 0x11 AirSyncBase

View File

@ -51,7 +51,7 @@ public class EasEmailSyncAdapterTests extends AndroidTestCase {
service.mContext = getContext(); service.mContext = getContext();
service.mMailbox = mailbox; service.mMailbox = mailbox;
service.mAccount = account; service.mAccount = account;
EasEmailSyncAdapter adapter = new EasEmailSyncAdapter(mailbox); EasEmailSyncAdapter adapter = new EasEmailSyncAdapter(mailbox, service);
EasEmailSyncParser p; EasEmailSyncParser p;
p = adapter.new EasEmailSyncParser(getTestInputStream(), service); p = adapter.new EasEmailSyncParser(getTestInputStream(), service);
// Test a few known types // Test a few known types

View File

@ -0,0 +1,45 @@
/*
* Copyright (C) 2009 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 com.android.exchange;
import com.android.email.provider.EmailContent.Account;
import com.android.email.provider.EmailContent.Mailbox;
import com.android.exchange.adapter.EasEmailSyncAdapter;
import com.android.exchange.adapter.EasTags;
import com.android.exchange.adapter.EasEmailSyncAdapter.EasEmailSyncParser;
import android.test.AndroidTestCase;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
public class EasTagsTests extends AndroidTestCase {
// Make sure there are no duplicates in the tags table
public void testNoDuplicates() {
String[][] allTags = EasTags.pages;
HashMap<String, Boolean> map = new HashMap<String, Boolean>();
for (String[] page: allTags) {
for (String tag: page) {
assertTrue(!map.containsKey(tag));
map.put(tag, true);
}
}
}
}