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.WRITE_EXTERNAL_STORAGE" />
<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 -->
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
@ -174,6 +175,17 @@
>
</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 -->
<service
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.R;
import com.android.email.mail.Store;
import com.android.email.mail.store.ExchangeStore;
import com.android.email.provider.EmailContent;
import android.app.Activity;
@ -120,6 +121,11 @@ public class AccountSetupOptions extends Activity implements OnClickListener {
mAccount.setSyncLookback(window);
}
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);
Email.setServicesEnabled(this);
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.Store;
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.exchange.Eas;
import com.android.exchange.SyncManager;
@ -115,6 +117,34 @@ public class ExchangeStore extends Store {
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
public Folder getFolder(String name) throws MessagingException {
synchronized (mFolders) {
@ -287,32 +317,6 @@ public class ExchangeStore extends Store {
} else {
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) {
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.NetworkErrorException;
import android.app.Service;
import android.content.Intent;
import android.content.Context;
import android.content.pm.PermissionInfo;
import android.content.pm.PackageManager;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
@ -38,6 +36,8 @@ import android.os.IBinder;
* password. We will need to implement confirmPassword, confirmCredentials, and updateCredentials.
*/
public class EasAuthenticatorService extends Service {
public static final String OPTIONS_USERNAME = "username";
public static final String OPTIONS_PASSWORD = "password";
class EasAuthenticator extends AbstractAccountAuthenticator {
public EasAuthenticator(Context context) {
@ -50,8 +50,8 @@ public class EasAuthenticatorService extends Service {
throws NetworkErrorException {
// The Bundle we are passed has username and password set
AccountManager.get(EasAuthenticatorService.this).blockingAddAccountExplicitly(
new Account(options.getString("username"), Eas.ACCOUNT_MANAGER_TYPE),
options.getString("password"), null);
new Account(options.getString(OPTIONS_USERNAME), Eas.ACCOUNT_MANAGER_TYPE),
options.getString(OPTIONS_PASSWORD), null);
Bundle b = new Bundle();
b.putString(Constants.ACCOUNT_NAME_KEY, options.getString("username"));
b.putString(Constants.ACCOUNT_TYPE_KEY, Eas.ACCOUNT_MANAGER_TYPE);
@ -84,7 +84,7 @@ public class EasAuthenticatorService extends Service {
@Override
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;
}
@ -107,7 +107,7 @@ public class EasAuthenticatorService extends Service {
public IBinder onBind(Intent intent) {
// TODO Replace this with an appropriate constant in AccountManager, when it's created
String authenticatorIntent = "android.accounts.AccountAuthenticator";
if (authenticatorIntent.equals(intent.getAction())) {
return new EasAuthenticator(this).getIAccountAuthenticator().asBinder();
} 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);
}
@SuppressWarnings("deprecation")
private String makeUriString(String cmd, String extra) {
// Cache the authentication string and the command string
if (mDeviceId == null)
@ -691,7 +692,7 @@ public class EasSyncService extends InteractiveSyncService {
BufferedReader rdr = null;
String id;
if (f.exists() && f.canRead()) {
rdr = new BufferedReader(new FileReader(f));
rdr = new BufferedReader(new FileReader(f), 128);
id = rdr.readLine();
rdr.close();
return id;
@ -853,7 +854,9 @@ public class EasSyncService extends InteractiveSyncService {
mAccount = Account.restoreAccountWithId(mContext, mAccount.mId);
mMailbox = Mailbox.restoreMailboxWithId(mContext, mMailbox.mId);
try {
if (mMailbox.mServerId.equals(Eas.ACCOUNT_MAILBOX)) {
if (mMailbox == null || mAccount == null) {
return;
} else if (mMailbox.mServerId.equals(Eas.ACCOUNT_MAILBOX)) {
runMain();
} else {
EasSyncAdapter target;
@ -861,9 +864,9 @@ public class EasSyncService extends InteractiveSyncService {
mProtocolVersion = mAccount.mProtocolVersion;
mProtocolVersionDouble = Double.parseDouble(mProtocolVersion);
if (mMailbox.mType == Mailbox.TYPE_CONTACTS)
target = new EasContactsSyncAdapter(mMailbox);
target = new EasContactsSyncAdapter(mMailbox, this);
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
// and we've missed that opportunity...

View File

@ -31,7 +31,7 @@ import android.util.Log;
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.
*
* 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
* 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).
* 4) USAR Receiver's onReceive method is called
* 5) USAR goes through all change and deletion records and compiles a list of mailboxes which have
* 4) ESAR Receiver's onReceive method is called
* 5) ESAR goes through all change and deletion records and compiles a list of mailboxes which have
* 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};
private static String TAG = "UserSyncAlarm";
private static String TAG = "EmailSyncAlarm";
@Override
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
* 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
* application.
*
@ -91,17 +91,17 @@ public class SyncManager extends Service implements Runnable {
MessageObserver mMessageObserver;
String mNextWaitReason;
IEmailServiceCallback mCallback;
RemoteCallbackList<IEmailServiceCallback> mCallbackList =
new RemoteCallbackList<IEmailServiceCallback>();
static private HashMap<Long, Boolean> mWakeLocks = new HashMap<Long, Boolean>();
static private HashMap<Long, PendingIntent> mPendingIntents =
new HashMap<Long, PendingIntent>();
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
* 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 {
// mAccounts keeps track of Accounts that we care about (EAS for now)
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) {
super(handler);
Context context = getContext();
@ -237,6 +237,7 @@ public class SyncManager extends Service implements Runnable {
return false;
}
@Override
public void onChange(boolean selfChange) {
// 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
@ -328,6 +329,7 @@ public class SyncManager extends Service implements Runnable {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
// See if there's anything to do...
kick();
@ -337,7 +339,7 @@ public class SyncManager extends Service implements Runnable {
class SyncedMessageObserver extends ContentObserver {
long maxChangedId = 0;
long maxDeletedId = 0;
Intent syncAlarmIntent = new Intent(INSTANCE, UserSyncAlarmReceiver.class);
Intent syncAlarmIntent = new Intent(INSTANCE, EmailSyncAlarmReceiver.class);
PendingIntent syncAlarmPendingIntent =
PendingIntent.getBroadcast(INSTANCE, 0, syncAlarmIntent, 0);
AlarmManager alarmManager = (AlarmManager)INSTANCE.getSystemService(Context.ALARM_SERVICE);
@ -347,6 +349,7 @@ public class SyncManager extends Service implements Runnable {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
INSTANCE.log("SyncedMessage changed: (re)setting alarm for 10s");
alarmManager.set(AlarmManager.RTC_WAKEUP,
@ -360,6 +363,7 @@ public class SyncManager extends Service implements Runnable {
super(handler);
}
@Override
public void onChange(boolean selfChange) {
INSTANCE.log("MessageObserver");
// 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;
}
static public AccountList getAccountList() {
if (INSTANCE != null) {
return INSTANCE.mAccountObserver.mAccounts;
} else {
return null;
}
}
public class SyncStatus {
static public final int NOT_RUNNING = 0;
static public final int DIED = 1;
@ -651,13 +663,6 @@ public class SyncManager extends Service implements Runnable {
public void run() {
mStop = false;
// if (Debug.isDebuggerConnected()) {
// try {
// Thread.sleep(10000L);
// } catch (InterruptedException e) {
// }
// }
runAwake(-1);
ContentResolver resolver = getContentResolver();
@ -668,7 +673,7 @@ public class SyncManager extends Service implements Runnable {
ConnectivityReceiver cr = new ConnectivityReceiver();
registerReceiver(cr, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
ConnectivityManager cm =
ConnectivityManager cm =
(ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
try {
@ -721,6 +726,22 @@ public class SyncManager extends Service implements Runnable {
}
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 now = System.currentTimeMillis();
// Start up threads that need it...
@ -800,7 +821,7 @@ public class SyncManager extends Service implements Runnable {
}
return nextWait;
}
static public void serviceRequest(Mailbox m) {
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
* an error state
*
*
* @param mailboxId
* @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;
}
static public int getSyncStatus(long mailboxId) {
synchronized (mSyncToken) {
if (INSTANCE == null || INSTANCE.mServiceMap == null) {
@ -984,30 +1005,32 @@ public class SyncManager extends Service implements Runnable {
* @param svc the service that is finished
*/
static public void done(AbstractSyncService svc) {
long mailboxId = svc.mMailboxId;
HashMap<Long, SyncError> errorMap = INSTANCE.mSyncErrorMap;
SyncError syncError = errorMap.get(mailboxId);
INSTANCE.mServiceMap.remove(mailboxId);
int exitStatus = svc.mExitStatus;
switch (exitStatus) {
case AbstractSyncService.EXIT_DONE:
if (!svc.mPartRequests.isEmpty()) {
// TODO Handle this case
}
errorMap.remove(mailboxId);
break;
case AbstractSyncService.EXIT_IO_ERROR:
if (syncError != null) {
syncError.escalate();
} else {
errorMap.put(mailboxId, INSTANCE.new SyncError(exitStatus, false));
}
kick();
break;
case AbstractSyncService.EXIT_LOGIN_FAILURE:
case AbstractSyncService.EXIT_EXCEPTION:
errorMap.put(mailboxId, INSTANCE.new SyncError(exitStatus, true));
break;
synchronized(mSyncToken) {
long mailboxId = svc.mMailboxId;
HashMap<Long, SyncError> errorMap = INSTANCE.mSyncErrorMap;
SyncError syncError = errorMap.get(mailboxId);
INSTANCE.mServiceMap.remove(mailboxId);
int exitStatus = svc.mExitStatus;
switch (exitStatus) {
case AbstractSyncService.EXIT_DONE:
if (!svc.mPartRequests.isEmpty()) {
// TODO Handle this case
}
errorMap.remove(mailboxId);
break;
case AbstractSyncService.EXIT_IO_ERROR:
if (syncError != null) {
syncError.escalate();
} else {
errorMap.put(mailboxId, INSTANCE.new SyncError(exitStatus, false));
}
kick();
break;
case AbstractSyncService.EXIT_LOGIN_FAILURE:
case AbstractSyncService.EXIT_EXCEPTION:
errorMap.put(mailboxId, INSTANCE.new SyncError(exitStatus, true));
break;
}
}
}
@ -1034,6 +1057,6 @@ public class SyncManager extends Service implements Runnable {
if (INSTANCE == null) {
return null;
}
return (Context)INSTANCE;
return INSTANCE;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -51,7 +51,7 @@ public class EasEmailSyncAdapterTests extends AndroidTestCase {
service.mContext = getContext();
service.mMailbox = mailbox;
service.mAccount = account;
EasEmailSyncAdapter adapter = new EasEmailSyncAdapter(mailbox);
EasEmailSyncAdapter adapter = new EasEmailSyncAdapter(mailbox, service);
EasEmailSyncParser p;
p = adapter.new EasEmailSyncParser(getTestInputStream(), service);
// 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);
}
}
}
}