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:
parent
9150b3005f
commit
948c36f47a
|
@ -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"
|
||||
|
|
|
@ -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"
|
||||
/>
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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...
|
||||
|
|
|
@ -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) {
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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);
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue