Major refactor and cleanup of EAS code

* Rewrote push logic to encompass multiple folders (i.e. calendar/contacts)
    * Change inbox from push frequency to ping frequency after initial sync
    * Implement upsync logic for email (i.e. sending changes to the server)
    * Did cleanup of some files (there's still some to do) re: format, style
    * Initial one-way sync of Contacts data - add and delete are implemented
    * Created adapter package for all parts of the EAS adapter
    * Created utility package for utility code that will eventually be merged
      with code in the Email application (Base64, QuotedPrintable, etc.)
    * SyncManager/AbstractSyncService can be used in the future for other
      protocols, especially IMAP push
This commit is contained in:
Marc Blank 2009-07-05 12:54:49 -07:00
parent 7c3cca80a0
commit b6493a07ef
40 changed files with 4383 additions and 2847 deletions

View File

@ -24,6 +24,9 @@
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- For EAS purposes; could be removed when EAS has a permanent home -->
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<!-- Only required if a store implements push mail and needs to keep network open -->
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
@ -150,7 +153,9 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<receiver android:name=".service.BootReceiver"
<receiver android:name="com.android.exchange.UserSyncAlarmReceiver"/>
<receiver android:name="com.android.exchange.MailboxAlarmReceiver"/>
<receiver android:name=".service.BootReceiver"
android:enabled="false"
>
<intent-filter>

View File

@ -106,7 +106,6 @@ public class MessageList extends ListActivity implements OnItemClickListener, On
mListAdapter = new MessageListAdapter(this);
setListAdapter(mListAdapter);
mListView.setAdapter(mAdapter);
// TODO set title to "account > mailbox (#unread)"

View File

@ -41,6 +41,8 @@ public class MessagingException extends Exception {
public static final int GENERAL_SECURITY = 4;
/** Authentication failed */
public static final int AUTHENTICATION_FAILED = 5;
/** Attempt to create duplicate account */
public static final int DUPLICATE_ACCOUNT = 6;
protected int mExceptionType;

View File

@ -748,6 +748,7 @@ public abstract class EmailContent {
public static final int CHECK_INTERVAL_NEVER = -1;
public static final int CHECK_INTERVAL_PUSH = -2;
public static final int CHECK_INTERVAL_PING = -3;
public static final int SYNC_WINDOW_USER = -1;
@ -1658,7 +1659,7 @@ public abstract class EmailContent {
public static final int TYPE_JUNK = 7;
// Types after this are used for non-mail mailboxes (as in EAS)
public static final int TYPE_INVISIBLE = 0x40;
public static final int TYPE_NOT_EMAIL = 0x40;
public static final int TYPE_CALENDAR = 0x41;
public static final int TYPE_CONTACTS = 0x42;
public static final int TYPE_TASKS = 0x43;
@ -1756,9 +1757,9 @@ public abstract class EmailContent {
public static final String TABLE_NAME = "HostAuth";
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/hostauth");
private static final int FLAG_SSL = 1;
private static final int FLAG_TLS = 2;
private static final int FLAG_AUTHENTICATE = 4;
public static final int FLAG_SSL = 1;
public static final int FLAG_TLS = 2;
public static final int FLAG_AUTHENTICATE = 4;
public String mProtocol;
public String mAddress;

View File

@ -226,41 +226,56 @@ public class EmailProvider extends ContentProvider {
}
static void createMessageTable(SQLiteDatabase db) {
String s = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, "
+ SyncColumns.ACCOUNT_KEY + " integer, "
+ SyncColumns.SERVER_ID + " integer, "
+ SyncColumns.SERVER_VERSION + " integer, "
+ SyncColumns.DATA + " text, "
+ SyncColumns.DIRTY_COUNT + " integer, "
+ MessageColumns.DISPLAY_NAME + " text, "
+ MessageColumns.TIMESTAMP + " integer, "
+ MessageColumns.SUBJECT + " text, "
+ MessageColumns.PREVIEW + " text, "
+ MessageColumns.FLAG_READ + " integer, "
+ MessageColumns.FLAG_LOADED + " integer, "
+ MessageColumns.FLAG_FAVORITE + " integer, "
+ MessageColumns.FLAG_ATTACHMENT + " integer, "
+ MessageColumns.FLAGS + " integer, "
+ MessageColumns.TEXT_INFO + " text, "
+ MessageColumns.HTML_INFO + " text, "
+ MessageColumns.CLIENT_ID + " integer, "
+ MessageColumns.MESSAGE_ID + " text, "
+ MessageColumns.THREAD_ID + " text, "
+ MessageColumns.MAILBOX_KEY + " integer, "
+ MessageColumns.ACCOUNT_KEY + " integer, "
+ MessageColumns.REFERENCE_KEY + " integer, "
+ MessageColumns.SENDER_LIST + " text, "
+ MessageColumns.FROM_LIST + " text, "
+ MessageColumns.TO_LIST + " text, "
+ MessageColumns.CC_LIST + " text, "
+ MessageColumns.BCC_LIST + " text, "
+ MessageColumns.REPLY_TO_LIST + " text"
+ ");";
String messageColumns = MessageColumns.DISPLAY_NAME + " text, "
+ MessageColumns.TIMESTAMP + " integer, "
+ MessageColumns.SUBJECT + " text, "
+ MessageColumns.PREVIEW + " text, "
+ MessageColumns.FLAG_READ + " integer, "
+ MessageColumns.FLAG_LOADED + " integer, "
+ MessageColumns.FLAG_FAVORITE + " integer, "
+ MessageColumns.FLAG_ATTACHMENT + " integer, "
+ MessageColumns.FLAGS + " integer, "
+ MessageColumns.TEXT_INFO + " text, "
+ MessageColumns.HTML_INFO + " text, "
+ MessageColumns.CLIENT_ID + " integer, "
+ MessageColumns.MESSAGE_ID + " text, "
+ MessageColumns.THREAD_ID + " text, "
+ MessageColumns.MAILBOX_KEY + " integer, "
+ MessageColumns.ACCOUNT_KEY + " integer, "
+ MessageColumns.REFERENCE_KEY + " integer, "
+ MessageColumns.SENDER_LIST + " text, "
+ MessageColumns.FROM_LIST + " text, "
+ MessageColumns.TO_LIST + " text, "
+ MessageColumns.CC_LIST + " text, "
+ MessageColumns.BCC_LIST + " text, "
+ MessageColumns.REPLY_TO_LIST + " text"
+ ");";
// This String and the following String MUST have the same columns, except for the type
// of those columns!
String createString = " (" + EmailContent.RECORD_ID + " integer primary key autoincrement, "
+ SyncColumns.ACCOUNT_KEY + " integer, "
+ SyncColumns.SERVER_ID + " integer, "
+ SyncColumns.SERVER_VERSION + " integer, "
+ SyncColumns.DATA + " text, "
+ SyncColumns.DIRTY_COUNT + " integer, "
+ messageColumns;
// For the updated and deleted tables, the id is assigned, but we do want to keep track
// of the ORDER of updates using an autoincrement primary key. We use the DATA column
// at this point; it has no other function
String altCreateString = " (" + EmailContent.RECORD_ID + " integer, "
+ SyncColumns.ACCOUNT_KEY + " integer, "
+ SyncColumns.SERVER_ID + " integer, "
+ SyncColumns.SERVER_VERSION + " integer, "
+ SyncColumns.DATA + " integer primary key autoincrement, "
+ SyncColumns.DIRTY_COUNT + " integer, "
+ messageColumns;
// The three tables have the same schema
db.execSQL("create table " + Message.TABLE_NAME + s);
db.execSQL("create table " + Message.UPDATED_TABLE_NAME + s);
db.execSQL("create table " + Message.DELETED_TABLE_NAME + s);
db.execSQL("create table " + Message.TABLE_NAME + createString);
db.execSQL("create table " + Message.UPDATED_TABLE_NAME + altCreateString);
db.execSQL("create table " + Message.DELETED_TABLE_NAME + altCreateString);
// For now, indices only on the Message table
db.execSQL("create index message_" + MessageColumns.TIMESTAMP
@ -364,7 +379,7 @@ public class EmailProvider extends ContentProvider {
+ MailboxColumns.FLAG_VISIBLE + " integer, "
+ MailboxColumns.FLAGS + " integer, "
+ MailboxColumns.VISIBLE_LIMIT + " integer"
+ ");";
+ ");";
db.execSQL("create table " + Mailbox.TABLE_NAME + s);
db.execSQL("create index mailbox_" + MailboxColumns.SERVER_ID
+ " on " + Mailbox.TABLE_NAME + " (" + MailboxColumns.SERVER_ID + ")");
@ -374,7 +389,7 @@ public class EmailProvider extends ContentProvider {
db.execSQL("create trigger mailbox_delete before delete on " + Mailbox.TABLE_NAME +
" begin delete from " + Message.TABLE_NAME +
" where " + MessageColumns.MAILBOX_KEY + "=old." + EmailContent.RECORD_ID +
"; end");
"; end");
}
static void upgradeMailboxTable(SQLiteDatabase db, int oldVersion, int newVersion) {
@ -518,7 +533,7 @@ public class EmailProvider extends ContentProvider {
Log.v(TAG, "EmailProvider.delete: uri=" + uri + ", match is " + match);
}
int result;
int result = -1;
try {
switch (match) {
@ -728,8 +743,8 @@ public class EmailProvider extends ContentProvider {
switch (match) {
case BODY:
case MESSAGE:
case DELETED_MESSAGE:
case UPDATED_MESSAGE:
case DELETED_MESSAGE:
case ATTACHMENT:
case MAILBOX:
case ACCOUNT:
@ -844,7 +859,7 @@ public class EmailProvider extends ContentProvider {
* update/insert/delete calls?
*/
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
throws OperationApplicationException {
throws OperationApplicationException {
SQLiteDatabase db = getDatabase(getContext());
db.beginTransaction();
try {

View File

@ -0,0 +1,330 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to 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 java.util.ArrayList;
import com.android.email.Email;
import com.android.email.mail.MessagingException;
import com.android.exchange.EmailContent.Account;
import com.android.exchange.EmailContent.Mailbox;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.util.Log;
/**
* Base class for all protocol services SyncManager (extends Service, implements
* Runnable) instantiates subclasses to run a sync (either timed, or push, or
* mail placed in outbox, etc.) EasSyncService is currently implemented; my goal
* would be to move IMAP to this structure when it comes time to introduce push
* functionality.
*/
public abstract class AbstractSyncService implements Runnable {
public String TAG = "ProtocolService";
public static final String SUMMARY_PROTOCOL = "_SUMMARY_";
public static final String SYNCED_PROTOCOL = "_SYNCING_";
public static final String MOVE_FAVORITES_PROTOCOL = "_MOVE_FAVORITES_";
public static final int CONNECT_TIMEOUT = 30000;
public static final int NETWORK_WAIT = 15000;
public static final int SECS = 1000;
public static final int MINS = 60 * SECS;
public static final int HRS = 60 * MINS;
public static final int DAYS = 24 * HRS;
public static final String IMAP_PROTOCOL = "imap";
public static final String EAS_PROTOCOL = "eas";
public static final int EXIT_DONE = 0;
public static final int EXIT_IO_ERROR = 1;
public static final int EXIT_LOGIN_FAILURE = 2;
public static final int EXIT_EXCEPTION = 3;
// Making SSL connections is so slow that I'd prefer that only one be
// executed at a time
// Kindly subclasses will synchronize on this before making an SSL
// connection
public static Object sslGovernorToken = new Object();
public Mailbox mMailbox;
protected long mMailboxId;
protected Thread mThread;
protected int mExitStatus = EXIT_EXCEPTION;
protected String mMailboxName;
public Account mAccount;
protected Context mContext;
protected long mRequestTime = 0;
protected ArrayList<PartRequest> mPartRequests = new ArrayList<PartRequest>();
protected PartRequest mPendingPartRequest = null;
/**
* Sent by SyncManager to request that the service stop itself cleanly
*/
public abstract void stop();
/**
* Sent by SyncManager to indicate a user request requiring service has been
* added to the service's pending request queue
*/
public abstract void ping();
/**
* Called to validate an account; abstract to allow each protocol to do what
* is necessary. For consistency with the Email app's original
* functionality, success is indicated by a failure to throw an Exception
* (ugh). Parameters are self-explanatory
*
* @param host
* @param userName
* @param password
* @param port
* @param ssl
* @param context
* @throws MessagingException
*/
public abstract void validateAccount(String host, String userName, String password, int port,
boolean ssl, Context context) throws MessagingException;
/**
* Sent by SyncManager to determine the state of a running sync This is
* currently unused
*
* @return status code
*/
public int getSyncStatus() {
return 0;
}
public AbstractSyncService(Context _context, Mailbox _mailbox) {
mContext = _context;
mMailbox = _mailbox;
mMailboxId = _mailbox.mId;
mMailboxName = _mailbox.mServerId;
mAccount = Account.restoreAccountWithId(_context, _mailbox.mAccountKey);
}
// Will be required when subclasses are instantiated by name
public AbstractSyncService(String prefix) {
}
/**
* The UI can call this static method to perform account validation. This method wraps each
* protocol's validateAccount method. Arguments are self-explanatory, except where noted.
*
* @param klass the protocol class (EasSyncService.class for example)
* @param host
* @param userName
* @param password
* @param port
* @param ssl
* @param context
* @throws MessagingException
*/
static public void validate(Class<? extends AbstractSyncService> klass, String host,
String userName, String password, int port, boolean ssl, Context context)
throws MessagingException {
AbstractSyncService svc;
try {
svc = klass.newInstance();
svc.validateAccount(host, userName, password, port, ssl, context);
} catch (IllegalAccessException e) {
throw new MessagingException("internal error", e);
} catch (InstantiationException e) {
throw new MessagingException("internal error", e);
}
}
public static class ValidationResult {
static final int NO_FAILURE = 0;
static final int CONNECTION_FAILURE = 1;
static final int VALIDATION_FAILURE = 2;
static final int EXCEPTION = 3;
static final ValidationResult succeeded = new ValidationResult(true, NO_FAILURE, null);
boolean success;
int failure = NO_FAILURE;
String reason = null;
Exception exception = null;
ValidationResult(boolean _success, int _failure, String _reason) {
success = _success;
failure = _failure;
reason = _reason;
}
ValidationResult(boolean _success) {
success = _success;
}
ValidationResult(Exception e) {
success = false;
failure = EXCEPTION;
exception = e;
}
public boolean isSuccess() {
return success;
}
public String getReason() {
return reason;
}
}
/**
* Asks SyncManager for a WaitLock for this sync
*/
public final void runAwake() {
//SyncManager.runAwake(mMailboxId);
}
/**
* Asks SyncManager to release any WaitLock and schedule an alarm at a specified number
* of milliseconds in the future
*
* @param millis
*/
public final void runAsleep(long millis) {
//SyncManager.runAsleep(mMailboxId, millis);
}
/**
* Convenience method to do user logging (i.e. connection activity). Saves a bunch of
* repetitive code.
*
* @param str the String to log
*/
public void userLog(String str) {
if (Eas.USER_DEBUG) {
Log.i(TAG, str);
}
}
public void errorLog(String str) {
if (Eas.USER_DEBUG) {
Log.e(TAG, str);
}
}
/**
* Convenience method to do test logging. Saves a bunch of repetitive code.
* Unlike user logging, TEST_DEBUG is declared final, so that testLog calls should get
* "compiled out" for non-debug builds.
*
* @param str the String to log
*/
protected void testLog(String str) {
if (Eas.TEST_DEBUG) {
Log.v(Email.LOG_TAG, str);
}
}
/**
* Implements a delay until there is some kind of network connectivity available. This method
* may be supplanted by functionality in SyncManager.
*
* @return the type of network connected to
*/
public int waitForConnectivity() {
ConnectivityManager cm = (ConnectivityManager)mContext
.getSystemService(Context.CONNECTIVITY_SERVICE);
while (true) {
NetworkInfo info = cm.getActiveNetworkInfo();
if (info != null && info.isConnected()) {
DetailedState state = info.getDetailedState();
if (state == DetailedState.CONNECTED) {
return info.getType();
} else {
// TODO Happens sometimes; find out why...
userLog("Not quite connected? Pause 1 second");
}
pause(1000);
} else {
userLog("Not connected; waiting 15 seconds");
pause(NETWORK_WAIT);
}
}
}
/**
* Convenience method to generate a small wait
*
* @param ms time to wait in milliseconds
*/
private void pause(int ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
}
}
// What's below here is temporary
/**
* PartRequest handling (common functionality)
* Can be overridden if desired, but IMAP/EAS both use the next three methods as-is
*/
public void addPartRequest(PartRequest req) {
synchronized (mPartRequests) {
mPartRequests.add(req);
}
}
public void removePartRequest(PartRequest req) {
synchronized (mPartRequests) {
mPartRequests.remove(req);
}
}
public PartRequest hasPartRequest(long emailId, String part) {
synchronized (mPartRequests) {
for (PartRequest pr : mPartRequests) {
if (pr.emailId == emailId && pr.loc.equals(part))
return pr;
}
}
return null;
}
// CancelPartRequest is sent in response to user input to stop a request
// (attachment load at this point)
// that is in progress. This will almost certainly require code overriding
// the base functionality, as
// sockets may need to be closed, etc. and this functionality will be
// service dependent. This returns
// the canceled PartRequest or null
public PartRequest cancelPartRequest(long emailId, String part) {
synchronized (mPartRequests) {
PartRequest p = null;
for (PartRequest pr : mPartRequests) {
if (pr.emailId == emailId && pr.loc.equals(part)) {
p = pr;
break;
}
}
if (p != null) {
mPartRequests.remove(p);
return p;
}
}
return null;
}
}

View File

@ -0,0 +1,62 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to 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;
/**
* Constants used throughout the EAS implementation are stored here.
*
*/
public class Eas {
// For use in collecting user logs
public static boolean USER_DEBUG = false; // DO NOT CHECK IN WITH THIS SET TO TRUE
// For temporary use while debugging
public static boolean TEST_DEBUG = false; // DO NOT CHECK IN WITH THIS SET TO TRUE
public static String VERSION = "0.1";
// From EAS spec
// Mail Cal
// 0 No filter Yes Yes
// 1 1 day ago Yes No
// 2 3 days ago Yes No
// 3 1 week ago Yes No
// 4 2 weeks ago Yes Yes
// 5 1 month ago Yes Yes
// 6 3 months ago No Yes
// 7 6 months ago No Yes
static final String FILTER_ALL = "0";
static final String FILTER_1_DAY = "1";
static final String FILTER_3_DAYS = "2";
static final String FILTER_1_WEEK = "3";
static final String FILTER_2_WEEKS = "4";
static final String FILTER_1_MONTH = "5";
static final String FILTER_3_MONTHS = "6";
static final String FILTER_6_MONTHS = "7";
static final String BODY_PREFERENCE_TEXT = "1";
static final String BODY_PREFERENCE_HTML = "2";
static final String DEFAULT_BODY_TRUNCATION_SIZE = "50000";
public static final int FOLDER_STATUS_OK = 1;
public static final int FOLDER_STATUS_INVALID_KEY = 9;
public void setUserDebug(boolean state) {
USER_DEBUG = state;
}
}

View File

@ -1,356 +0,0 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to 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 java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import com.android.email.provider.EmailProvider;
import com.android.exchange.EmailContent.Account;
import com.android.exchange.EmailContent.Attachment;
import com.android.exchange.EmailContent.Mailbox;
import com.android.exchange.EmailContent.Message;
import com.android.exchange.EmailContent.MessageColumns;
import com.android.exchange.EmailContent.SyncColumns;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.os.RemoteException;
import android.util.Log;
public class EasEmailSyncParser extends EasParser {
private static final String TAG = "EmailSyncParser";
private Account mAccount;
private EasService mService;
private ContentResolver mContentResolver;
private Context mContext;
private Mailbox mMailbox;
protected boolean mMoreAvailable = false;
String[] bindArgument = new String[1];
public EasEmailSyncParser(InputStream in, EasService service) throws IOException {
super(in);
mService = service;
mContext = service.mContext;
mMailbox = service.mMailbox;
mAccount = service.mAccount;
//setDebug(true);
mContentResolver = mContext.getContentResolver();
}
public void parse() throws IOException {
int status;
if (nextTag(START_DOCUMENT) != EasTags.SYNC_SYNC)
throw new IOException();
while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
if (tag == EasTags.SYNC_COLLECTION || tag == EasTags.SYNC_COLLECTIONS) {
// Ignore
} else if (tag == EasTags.SYNC_STATUS) {
status = getValueInt();
if (status != 1) {
System.err.println("Sync failed: " + status);
if (status == 3) {
// TODO Bad sync key. Must delete everything and start over...?
mMailbox.mSyncKey = "0";
Log.w(TAG, "Bad sync key; RESET and delete mailbox contents");
mContext.getContentResolver()
.delete(Message.CONTENT_URI,
Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
mMoreAvailable = true;
}
}
} else if (tag == EasTags.SYNC_COMMANDS) {
commandsParser();
} else if (tag == EasTags.SYNC_RESPONSES) {
skipTag();
} else if (tag == EasTags.SYNC_MORE_AVAILABLE) {
mMoreAvailable = true;
} else if (tag == EasTags.SYNC_SYNC_KEY) {
if (mMailbox.mSyncKey.equals("0"))
mMoreAvailable = true;
mMailbox.mSyncKey = getValue();
} else
skipTag();
}
mMailbox.saveOrUpdate(mContext);
}
public void addParser(ArrayList<Message> emails) throws IOException {
Message msg = new Message();
String to = "";
String from = "";
String cc = "";
String replyTo = "";
int size = 0;
msg.mAccountKey = mAccount.mId;
msg.mMailboxKey = mMailbox.mId;
msg.mFlagLoaded = Message.LOADED;
ArrayList<Attachment> atts = new ArrayList<Attachment>();
boolean inData = false;
while (nextTag(EasTags.SYNC_ADD) != END) {
switch (tag) {
case EasTags.SYNC_SERVER_ID: // same as EasTags.EMAIL_BODY_SIZE
if (!inData) {
msg.mServerId = getValue();
} else {
size = Integer.parseInt(getValue());
}
break;
case EasTags.SYNC_APPLICATION_DATA:
inData = true;
break;
case EasTags.EMAIL_ATTACHMENTS:
break;
case EasTags.EMAIL_ATTACHMENT:
attachmentParser(atts, msg);
break;
case EasTags.EMAIL_TO:
to = getValue();
break;
case EasTags.EMAIL_FROM:
from = getValue();
String sender = from;
int q = from.indexOf('\"');
if (q >= 0) {
int qq = from.indexOf('\"', q + 1);
if (qq > 0) {
sender = from.substring(q + 1, qq);
}
}
msg.mDisplayName = sender;
break;
case EasTags.EMAIL_CC:
cc = getValue();
break;
case EasTags.EMAIL_REPLY_TO:
replyTo = getValue();
break;
case EasTags.EMAIL_DATE_RECEIVED:
String date = getValue();
// 2009-02-11T18:03:03.627Z
GregorianCalendar cal = new GregorianCalendar();
cal.set(Integer.parseInt(date.substring(0, 4)),
Integer.parseInt(date.substring(5, 7)) - 1,
Integer.parseInt(date.substring(8, 10)),
Integer.parseInt(date.substring(11, 13)),
Integer.parseInt(date.substring(14, 16)),
Integer.parseInt(date.substring(17, 19)));
cal.setTimeZone(TimeZone.getTimeZone("GMT"));
msg.mTimeStamp = cal.getTimeInMillis();
break;
case EasTags.EMAIL_DISPLAY_TO:
break;
case EasTags.EMAIL_SUBJECT:
msg.mSubject = getValue();
break;
case EasTags.EMAIL_IMPORTANCE:
break;
case EasTags.EMAIL_READ:
msg.mFlagRead = getValueInt() == 1;
break;
case EasTags.EMAIL_BODY:
msg.mTextInfo = "X;X;8;" + size; // location;encoding;charset;size
msg.mText = getValue();
// For now...
msg.mPreview = "Fake preview"; //Messages.previewFromText(body);
break;
case EasTags.EMAIL_MESSAGE_CLASS:
break;
default:
skipTag();
}
}
// Tell the provider that this is synced back
msg.mServerVersion = mMailbox.mSyncKey;
msg.mTo = to;
msg.mFrom = from;
msg.mCc = cc;
msg.mReplyTo = replyTo;
if (atts.size() > 0) {
msg.mAttachments = atts;
}
emails.add(msg);
}
public void attachmentParser(ArrayList<Attachment> atts, Message msg)
throws IOException {
String fileName = null;
String length = null;
String lvl = null;
while (nextTag(EasTags.EMAIL_ATTACHMENT) != END) {
switch (tag) {
case EasTags.EMAIL_DISPLAY_NAME:
fileName = getValue();
break;
case EasTags.EMAIL_ATT_NAME:
lvl = getValue();
break;
case EasTags.EMAIL_ATT_SIZE:
length = getValue();
break;
default:
skipTag();
}
}
if (fileName != null && length != null && lvl != null) {
Attachment att = new Attachment();
att.mEncoding = "base64";
att.mSize = Long.parseLong(length);
att.mFileName = fileName;
atts.add(att);
msg.mFlagAttachment = true;
}
}
public void deleteParser(ArrayList<Long> deletes) throws IOException {
while (nextTag(EasTags.SYNC_DELETE) != END) {
switch (tag) {
case EasTags.SYNC_SERVER_ID:
String serverId = getValue();
Cursor c = mContentResolver.query(Message.CONTENT_URI,
Message.ID_COLUMN_PROJECTION,
SyncColumns.SERVER_ID + "=" + serverId, null, null);
try {
if (c.moveToFirst()) {
mService.log("Deleting " + serverId);
deletes.add(c.getLong(Message.ID_COLUMNS_ID_COLUMN));
}
} finally {
c.close();
}
break;
default:
skipTag();
}
}
}
public void changeParser(ArrayList<Long> changes) throws IOException {
String serverId = null;
boolean oldRead = false;
boolean read = true;
long id = 0;
while (nextTag(EasTags.SYNC_CHANGE) != END) {
switch (tag) {
case EasTags.SYNC_SERVER_ID:
serverId = getValue();
bindArgument[0] = serverId;
Cursor c = mContentResolver.query(Message.CONTENT_URI,
Message.LIST_PROJECTION,
SyncColumns.SERVER_ID + "=?", bindArgument, null);
try {
if (c.moveToFirst()) {
mService.log("Changing " + serverId);
oldRead = c.getInt(Message.LIST_READ_COLUMN) == Message.READ;
id = c.getLong(Message.LIST_ID_COLUMN);
}
} finally {
c.close();
}
break;
case EasTags.EMAIL_READ:
read = getValueInt() == 1;
break;
case EasTags.SYNC_APPLICATION_DATA:
break;
default:
skipTag();
}
}
if (oldRead != read) {
changes.add(id);
}
}
public void commandsParser() throws IOException {
ArrayList<Message> newEmails = new ArrayList<Message>();
ArrayList<Long> deletedEmails = new ArrayList<Long>();
ArrayList<Long> changedEmails = new ArrayList<Long>();
while (nextTag(EasTags.SYNC_COMMANDS) != END) {
if (tag == EasTags.SYNC_ADD) {
addParser(newEmails);
} else if (tag == EasTags.SYNC_DELETE) {
deleteParser(deletedEmails);
} else if (tag == EasTags.SYNC_CHANGE) {
changeParser(changedEmails);
} else
skipTag();
}
// Use a batch operation to handle the changes
// TODO Notifications
// TODO Store message bodies
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
for (Message content: newEmails) {
content.addSaveOps(ops);
}
for (Long id: deletedEmails) {
ops.add(ContentProviderOperation
.newDelete(ContentUris.withAppendedId(Message.CONTENT_URI, id)).build());
}
if (!changedEmails.isEmpty()) {
ContentValues cv = new ContentValues();
// TODO Handle proper priority
// Set this as the correct state (assuming server wins)
cv.put(SyncColumns.DIRTY_COUNT, 0);
cv.put(MessageColumns.FLAG_READ, true);
for (Long id: changedEmails) {
// For now, don't handle read->unread
ops.add(ContentProviderOperation.newUpdate(ContentUris
.withAppendedId(Message.CONTENT_URI, id)).withValues(cv).build());
}
}
ops.add(ContentProviderOperation.newUpdate(ContentUris
.withAppendedId(Mailbox.CONTENT_URI, mMailbox.mId))
.withValues(mMailbox.toContentValues()).build());
try {
ContentProviderResult[] results = mService.mContext.getContentResolver()
.applyBatch(EmailProvider.EMAIL_AUTHORITY, ops);
for (ContentProviderResult result: results) {
if (result.uri == null) {
Log.v(TAG, "Null result in ContentProviderResult!");
}
}
} catch (RemoteException e) {
// There is nothing to be done here; fail by returning null
} catch (OperationApplicationException e) {
// There is nothing to be done here; fail by returning null
}
Log.v(TAG, "Mailbox EOS syncKey now: " + mMailbox.mSyncKey);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Copyright (C) 2008-2009 Marc Blank
* Licensed to The Android Open Source Project.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -17,8 +17,6 @@
package com.android.exchange;
import java.io.IOException;
public class EodException extends IOException {
private static final long serialVersionUID = 1L;
public class EasException extends Exception {
private static final long serialVersionUID = 5894556952470989968L;
}

View File

@ -1,216 +0,0 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to 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 java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.android.email.provider.EmailProvider;
import com.android.exchange.EmailContent.Account;
import com.android.exchange.EmailContent.Mailbox;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentUris;
//import android.content.Context;
import android.content.OperationApplicationException;
import android.os.RemoteException;
import android.util.Log;
public class EasFolderSyncParser extends EasParser {
public static final String TAG = "FolderSyncParser";
public static final int USER_FOLDER_TYPE = 1;
public static final int INBOX_TYPE = 2;
public static final int DRAFTS_TYPE = 3;
public static final int DELETED_TYPE = 4;
public static final int SENT_TYPE = 5;
public static final int OUTBOX_TYPE = 6;
public static final int TASKS_TYPE = 7;
public static final int CALENDAR_TYPE = 8;
public static final int CONTACTS_TYPE = 9;
public static final int NOTES_TYPE = 10;
public static final int JOURNAL_TYPE = 11;
public static final int USER_MAILBOX_TYPE = 12;
public static final List<Integer> mMailFolderTypes =
Arrays.asList(INBOX_TYPE,DRAFTS_TYPE,DELETED_TYPE,SENT_TYPE,OUTBOX_TYPE,USER_MAILBOX_TYPE);
private Account mAccount;
private EasService mService;
//private Context mContext;
private MockParserStream mMock = null;
public EasFolderSyncParser(InputStream in, EasService service) throws IOException {
super(in);
mService = service;
mAccount = service.mAccount;
//mContext = service.mContext;
if (in instanceof MockParserStream) {
mMock = (MockParserStream)in;
}
}
public void parse() throws IOException {
//captureOn();
int status;
if (nextTag(START_DOCUMENT) != EasTags.FOLDER_FOLDER_SYNC)
throw new IOException();
while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
if (tag == EasTags.FOLDER_STATUS) {
status = getValueInt();
if (status != 1) {
System.err.println("FolderSync failed: " + status);
}
} else if (tag == EasTags.FOLDER_SYNC_KEY) {
mAccount.mSyncKey = getValue();
} else if (tag == EasTags.FOLDER_CHANGES) {
changesParser();
} else
skipTag();
}
//captureOff(mContext, "FolderSyncParser.txt");
}
public void addParser(ArrayList<Mailbox> boxes) throws IOException {
String name = null;
String serverId = null;
String parentId = null;
int type = 0;
while (nextTag(EasTags.FOLDER_ADD) != END) {
switch (tag) {
case EasTags.FOLDER_DISPLAY_NAME: {
name = getValue();
break;
}
case EasTags.FOLDER_TYPE: {
type = getValueInt();
break;
}
case EasTags.FOLDER_PARENT_ID: {
parentId = getValue();
break;
}
case EasTags.FOLDER_SERVER_ID: {
serverId = getValue();
break;
}
default:
skipTag();
}
}
if (mMailFolderTypes.contains(type)) {
Mailbox m = new Mailbox();
m.mDisplayName = name;
m.mServerId = serverId;
m.mAccountKey = mAccount.mId;
if (type == INBOX_TYPE) {
m.mSyncFrequency = Account.CHECK_INTERVAL_PUSH;
m.mType = Mailbox.TYPE_INBOX;
} else if (type == OUTBOX_TYPE) {
//m.mSyncFrequency = MailService.OUTBOX_FREQUENCY;
m.mSyncFrequency = Account.CHECK_INTERVAL_NEVER;
m.mType = Mailbox.TYPE_OUTBOX;
} else {
if (type == SENT_TYPE) {
m.mType = Mailbox.TYPE_SENT;
} else if (type == DRAFTS_TYPE) {
m.mType = Mailbox.TYPE_DRAFTS;
} else if (type == DELETED_TYPE) {
m.mType = Mailbox.TYPE_TRASH;
}
m.mSyncFrequency = Account.CHECK_INTERVAL_NEVER;
}
if (!parentId.equals("0")) {
m.mParentServerId = parentId;
}
Log.v(TAG, "Adding mailbox: " + m.mDisplayName);
boxes.add(m);
}
return;
}
public void changesParser() throws IOException {
// Keep track of new boxes, deleted boxes, updated boxes
ArrayList<Mailbox> newBoxes = new ArrayList<Mailbox>();
while (nextTag(EasTags.FOLDER_CHANGES) != END) {
if (tag == EasTags.FOLDER_ADD) {
addParser(newBoxes);
} else if (tag == EasTags.FOLDER_COUNT) {
getValueInt();
} else
skipTag();
}
for (Mailbox m: newBoxes) {
String parent = m.mParentServerId;
if (parent != null) {
// Wrong except first time! Need to check existing boxes!
//**PROVIDER
m.mFlagVisible = true; //false;
for (Mailbox mm: newBoxes) {
if (mm.mServerId.equals(parent)) {
//mm.parent = true;
}
}
}
}
if (mMock != null) {
mMock.setResult(newBoxes);
return;
}
if (!newBoxes.isEmpty()) {
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
for (Mailbox content: newBoxes) {
ContentProviderOperation.Builder b = ContentProviderOperation
.newInsert(Mailbox.CONTENT_URI);
b.withValues(content.toContentValues());
ops.add(b.build());
}
ops.add(ContentProviderOperation.newUpdate(ContentUris
.withAppendedId(Account.CONTENT_URI, mAccount.mId))
.withValues(mAccount.toContentValues()).build());
try {
ContentProviderResult[] results = mService.mContext.getContentResolver()
.applyBatch(EmailProvider.EMAIL_AUTHORITY, ops);
for (ContentProviderResult result: results) {
if (result.uri == null) {
return;
}
}
Log.v(TAG, "New syncKey: " + mAccount.mSyncKey);
} catch (RemoteException e) {
// There is nothing to be done here; fail by returning null
} catch (OperationApplicationException e) {
// There is nothing to be done here; fail by returning null
}
}
}
}

View File

@ -1,297 +0,0 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to 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 java.io.*;
import java.util.ArrayList;
import android.content.Context;
import android.util.Log;
public abstract class EasParser {
private static final String TAG = "EasParser";
public static final int START_DOCUMENT = 0;
public static final int DONE = 1;
public static final int START = 2;
public static final int END = 3;
public static final int TEXT = 4;
public static final int END_DOCUMENT = 3;
private static final int NOT_FETCHED = Integer.MIN_VALUE;
private static final int NOT_ENDED = Integer.MIN_VALUE;
private static final int EOF_BYTE = -1;
private boolean debug = false;
private boolean capture = false;
private ArrayList<Integer> captureArray;
private InputStream in;
private int depth;
private int nextId = NOT_FETCHED;
private String[] tagTable;
private String[][] tagTables = new String[24][];
private String[] nameArray = new String[32];
private int[] tagArray = new int[32];
private boolean noContent;
// Available to all to avoid method calls
public int endTag = NOT_ENDED;
public int type;
public int tag;
public String name;
public String text;
public int num;
public void parse () throws IOException {
}
public EasParser (InputStream in) throws IOException {
String[][] pages = EasTags.pages;
for (int i = 0; i < pages.length; i++) {
String[] page = pages[i];
if (page.length > 0) {
setTagTable(i, page);
}
}
setInput(in);
}
public void setDebug (boolean val) {
debug = val;
}
public void captureOn () {
capture = true;
captureArray = new ArrayList<Integer>();
}
public void captureOff (Context context, String file) {
try {
FileOutputStream out = context.openFileOutput(file, Context.MODE_WORLD_WRITEABLE);
out.write(captureArray.toString().getBytes());
out.close();
} catch (FileNotFoundException e) {
} catch (IOException e) {
}
}
public String getValue () throws IOException {
getNext(false);
String val = text;
getNext(false);
if (type != END) {
throw new IOException("No END found!");
}
endTag = tag;
return val;
}
public int getValueInt () throws IOException {
getNext(true);
int val = num;
getNext(false);
if (type != END) {
throw new IOException("No END found!");
}
endTag = tag;
return val;
}
public int nextTag (int endTag) throws IOException {
while (getNext(false) != DONE) {
if (type == START) {
return tag;
} else if (type == END && tag == endTag) {
return END;
}
}
if (endTag == START_DOCUMENT) {
return END_DOCUMENT;
}
throw new EodException();
}
public void skipTag () throws IOException {
int thisTag = tag;
while (getNext(false) != DONE) {
if (type == END && tag == thisTag) {
return;
}
}
throw new EofException();
}
public int nextToken() throws IOException {
getNext(false);
return type;
}
public void setInput(InputStream in) throws IOException {
this.in = in;
readByte(); // version
readInt(); // ?
readInt(); // 106 (UTF-8)
readInt(); // string table length
tagTable = tagTables[0];
}
public int next () throws IOException {
getNext(false);
return type;
}
private final int getNext(boolean asInt) throws IOException {
if (type == END) {
depth--;
} else {
endTag = NOT_ENDED;
}
if (noContent) {
type = END;
noContent = false;
return type;
}
text = null;
name = null;
int id = nextId ();
while (id == Wbxml.SWITCH_PAGE) {
nextId = NOT_FETCHED;
tagTable = tagTables[(readByte())];
id = nextId();
}
nextId = NOT_FETCHED;
switch (id) {
case -1 :
type = DONE;
break;
case Wbxml.END :
type = END;
if (debug) {
name = nameArray[depth];
Log.v(TAG, "</" + name + '>');
}
tag = endTag = tagArray[depth];
break;
case Wbxml.STR_I :
type = TEXT;
if (asInt) {
num = readInlineInt();
} else {
text = readInlineString();
}
if (debug) {
Log.v(TAG, asInt ? Integer.toString(num) : text);
}
break;
default :
type = START;
tag = id & 0x3F;
noContent = (id & 0x40) == 0;
depth++;
if (debug) {
name = tagTable[tag - 5];
Log.v(TAG, '<' + name + '>');
nameArray[depth] = name;
}
tagArray[depth] = tag;
}
return type;
}
private int read () throws IOException {
int i = in.read();
if (capture) {
captureArray.add(i);
}
return i;
}
private int nextId () throws IOException {
if (nextId == NOT_FETCHED) {
nextId = read();
}
return nextId;
}
private int readByte() throws IOException {
int i = read();
if (i == EOF_BYTE) {
throw new EofException();
}
return i;
}
private int readInlineInt() throws IOException {
int result = 0;
while (true) {
int i = readByte();
if (i == 0) {
return result;
}
if (i >= '0' && i <= '9') {
result = (result * 10) + (i - '0');
} else {
throw new IOException("Non integer");
}
}
}
private int readInt() throws IOException {
int result = 0;
int i;
do {
i = readByte();
result = (result << 7) | (i & 0x7f);
} while ((i & 0x80) != 0);
return result;
}
private String readInlineString() throws IOException {
StringBuilder sb = new StringBuilder(4096);
while (true){
int i = read();
if (i == 0) {
break;
} else if (i == EOF_BYTE) {
throw new EofException();
}
sb.append((char)i);
}
String res = sb.toString();
return res;
}
public void setTagTable(int page, String[] table) {
tagTables[page] = table;
}
}

View File

@ -1,103 +0,0 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to 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 java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import com.android.exchange.EmailContent.Mailbox;
import android.content.Context;
public class EasPingService extends EasService {
EasService mCaller;
HttpURLConnection mConnection = null;
public EasPingService(Context _context, Mailbox _mailbox, EasService _caller) {
super(_context, _mailbox);
mCaller = _caller;
mHostAddress = _caller.mHostAddress;
mUserName = _caller.mUserName;
mPassword = _caller.mPassword;
}
class EASPingParser extends EasParser {
protected boolean mMoreAvailable = false;
public EASPingParser(InputStream in, EasService service) throws IOException {
super(in);
mMailbox = service.mMailbox;
setDebug(true);
}
public void parse() throws IOException {
int status;
if (nextTag(START_DOCUMENT) != EasTags.PING_PING) {
throw new IOException();
}
while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
if (tag == EasTags.PING_STATUS) {
status = getValueInt();
log("Ping completed, status = " + status);
if (status == 1 || status == 2) {
}
mCaller.ping();
} else {
skipTag();
}
}
}
}
public void stop () {
mConnection.disconnect();
}
public void run () {
try {
EASSerializer s = new EASSerializer();
s.start("Ping").data("HeartbeatInterval", "900").start("PingFolders")
.start("PingFolder").data("PingId", mMailbox.mServerId).data("PingClass", "Email")
.end("PingFolder").end("PingFolders").end("Ping").end();
String data = s.toString();
HttpURLConnection uc = sendEASPostCommand("Ping", data);
mConnection = uc;
log("Sending ping, read timeout: " + uc.getReadTimeout() / 1000 + "s");
int code = uc.getResponseCode();
log("Response code: " + code);
if (code == HttpURLConnection.HTTP_OK) {
String encoding = uc.getHeaderField("Transfer-Encoding");
if (encoding == null) {
int len = uc.getHeaderFieldInt("Content-Length", 0);
if (len > 0) {
new EASPingParser(uc.getInputStream(), this).parse();
}
}
}
} catch (IOException e1) {
e1.printStackTrace();
} catch (RuntimeException e1) {
e1.printStackTrace();
}
mCaller.ping();
log(Thread.currentThread().getName() + " thread completed...");
}
}

View File

@ -1,800 +0,0 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to 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 java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URI;
import java.net.URL;
import java.util.Hashtable;
import javax.net.ssl.HttpsURLConnection;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.apache.http.impl.client.DefaultHttpClient;
import com.android.email.Account;
import com.android.email.mail.AuthenticationFailedException;
import com.android.email.mail.MessagingException;
import com.android.exchange.EmailContent.AttachmentColumns;
import com.android.exchange.EmailContent.HostAuth;
import com.android.exchange.EmailContent.Mailbox;
import com.android.exchange.EmailContent.Message;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
public class EasService extends ProtocolService {
public static final String TAG = "EasService";
private static final String WINDOW_SIZE = "10";
// From EAS spec
// Mail Cal
// 0 No filter Yes Yes
// 1 1 day ago Yes No
// 2 3 days ago Yes No
// 3 1 week ago Yes No
// 4 2 weeks ago Yes Yes
// 5 1 month ago Yes Yes
// 6 3 months ago No Yes
// 7 6 months ago No Yes
private static final String FILTER_ALL = "0";
private static final String FILTER_1_DAY = "1";
private static final String FILTER_3_DAYS = "2";
private static final String FILTER_1_WEEK = "3";
private static final String FILTER_2_WEEKS = "4";
private static final String FILTER_1_MONTH = "5";
//private static final String FILTER_3_MONTHS = "6";
//private static final String FILTER_6_MONTHS = "7";
private static final String BODY_PREFERENCE_TEXT = "1";
//private static final String BODY_PREFERENCE_HTML = "2";
// Reasonable to be static for now
static String mProtocolVersion = "12.0"; //"2.5";
static String mDeviceId = null;
static String mDeviceType = "Android";
String mAuthString = null;
String mCmdString = null;
String mVersions;
String mHostAddress;
String mUserName;
String mPassword;
boolean mSentCommands;
boolean mIsIdle = false;
Context mContext;
InputStream mPendingPartInputStream = null;
private boolean mStop = false;
private Object mWaitTarget = new Object();
public EasService (Context _context, Mailbox _mailbox) {
// A comment
super(_context, _mailbox);
mContext = _context;
}
private EasService (String prefix) {
super(prefix);
}
public EasService () {
this("EAS Validation");
}
@Override
public void ping() {
// TODO Auto-generated method stub
log("We've been pinged!");
synchronized (mWaitTarget) {
mWaitTarget.notify();
}
}
@Override
public void stop() {
// TODO Auto-generated method stub
mStop = true;
}
public int getSyncStatus () {
return 0;
}
public void validateAccount (String hostAddress, String userName, String password,
int port, boolean ssl, Context context) throws MessagingException {
try {
log("Testing EAS: " + hostAddress + ", " + userName + ", ssl = " + ssl);
EASSerializer s = new EASSerializer();
s.start("FolderSync").start("FolderSyncKey").text("0").end("FolderSyncKey")
.end("FolderSync").end();
String data = s.toString();
EasService svc = new EasService("%TestAccount%");
svc.mHostAddress = hostAddress;
svc.mUserName = userName;
svc.mPassword = password;
HttpURLConnection uc = svc.sendEASPostCommand("FolderSync", data);
int code = uc.getResponseCode();
Log.v(TAG, "Validation response code: " + code);
if (code == HttpURLConnection.HTTP_OK) {
return;
}
if (code == 401 || code == 403) {
Log.v(TAG, "Authentication failed");
throw new AuthenticationFailedException("Validation failed");
}
else {
//TODO Need to catch other kinds of errors (e.g. policy related)
Log.v(TAG, "Validation failed, reporting I/O error");
throw new MessagingException(MessagingException.IOERROR);
}
} catch (IOException e) {
Log.v(TAG, "IOException caught, reporting I/O error: " + e.getMessage());
throw new MessagingException(MessagingException.IOERROR);
}
}
protected HttpURLConnection sendEASPostCommand (String cmd, String data) throws IOException {
HttpURLConnection uc = setupEASCommand("POST", cmd);
if (uc != null) {
uc.setRequestProperty("Content-Length", Integer.toString(data.length() + 2));
OutputStreamWriter w = new OutputStreamWriter(uc.getOutputStream(), "UTF-8");
w.write(data);
w.write("\r\n");
w.flush();
w.close();
}
return uc;
}
static private final int CHUNK_SIZE = 16*1024;
protected void getAttachment (PartRequest req) throws IOException {
DefaultHttpClient client = new DefaultHttpClient();
String us = makeUriString("GetAttachment", "&AttachmentName=" + req.att.mLocation);
HttpPost method = new HttpPost(URI.create(us));
method.setHeader("Authorization", mAuthString);
HttpResponse res = client.execute(method);
int status = res.getStatusLine().getStatusCode();
if (status == HttpURLConnection.HTTP_OK) {
HttpEntity e = res.getEntity();
int len = (int)e.getContentLength();
String type = e.getContentType().getValue();
Log.v(TAG, "Attachment code: " + status + ", Length: " + len + ", Type: " + type);
InputStream is = res.getEntity().getContent();
File f = null; //Attachment.openAttachmentFile(req);
if (f != null) {
FileOutputStream os = new FileOutputStream(f);
if (len > 0) {
try {
mPendingPartRequest = req;
mPendingPartInputStream = is;
byte[] bytes = new byte[CHUNK_SIZE];
int length = len;
while (len > 0) {
int n = (len > CHUNK_SIZE ? CHUNK_SIZE : len);
int read = is.read(bytes, 0, n);
os.write(bytes, 0, read);
len -= read;
if (req.handler != null) {
long pct = ((length - len) * 100 / length);
req.handler.sendEmptyMessage((int)pct);
}
}
} finally {
mPendingPartRequest = null;
mPendingPartInputStream = null;
}
}
os.flush();
os.close();
ContentValues cv = new ContentValues();
cv.put(AttachmentColumns.CONTENT_URI, f.getAbsolutePath());
cv.put(AttachmentColumns.MIME_TYPE, type);
req.att.update(mContext, cv);
// TODO Inform UI that we're done
}
}
}
private HttpURLConnection setupEASCommand (String method, String cmd) {
return setupEASCommand(method, cmd, null);
}
private String makeUriString (String cmd, String extra) {
if (mAuthString == null) {
String cs = mUserName + ':' + mPassword;
mAuthString = "Basic " + Base64.encodeBytes(cs.getBytes());
mCmdString = "&User=" + mUserName + "&DeviceId=" + mDeviceId
+ "&DeviceType=" + mDeviceType;
}
boolean ssl = true;
// TODO Remove after testing
if (mHostAddress.equalsIgnoreCase("owa.electricmail.com")) {
ssl = false;
}
String scheme = ssl ? "https" : "http";
String us = scheme + "://" + mHostAddress + "/Microsoft-Server-ActiveSync";
if (cmd != null) {
us += "?Cmd=" + cmd + mCmdString;
}
if (extra != null) {
us += extra;
}
return us;
}
private HttpURLConnection setupEASCommand (String method, String cmd, String extra) {
// Hack for now
boolean ssl = true;
// TODO Remove this when no longer needed
if (mHostAddress.equalsIgnoreCase("owa.electricmail.com")) {
ssl = false;
}
try {
String us = makeUriString(cmd, extra);
URL u = new URL(us);
HttpURLConnection uc = (HttpURLConnection)u.openConnection();
try {
HttpURLConnection.setFollowRedirects(true);
} catch (Exception e) {
}
if (ssl) {
((HttpsURLConnection)uc).setHostnameVerifier(new AllowAllHostnameVerifier());
}
uc.setConnectTimeout(10*SECS);
uc.setReadTimeout(20*MINS);
if (method.equals("POST")) {
uc.setDoOutput(true);
}
uc.setRequestMethod(method);
uc.setRequestProperty("Authorization", mAuthString);
if (extra == null) {
if (cmd != null && cmd.startsWith("SendMail&")) {
uc.setRequestProperty("Content-Type", "message/rfc822");
} else {
uc.setRequestProperty("Content-Type", "application/vnd.ms-sync.wbxml");
}
uc.setRequestProperty("MS-ASProtocolVersion", mProtocolVersion);
uc.setRequestProperty("Connection", "keep-alive");
uc.setRequestProperty("User-Agent", mDeviceType + "/0.3");
} else {
uc.setRequestProperty("Content-Length", "0");
}
return uc;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (ProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
static class EASSerializer extends WbxmlSerializer {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
static Hashtable<String, Object> tagTable = null;
EASSerializer () {
super();
try {
setOutput(byteStream, null);
if (tagTable == null) {
String[][] pages = EasTags.pages;
for (int i = 0; i < pages.length; i++) {
String[] page = pages[i];
if (page.length > 0)
setTagTable(i, page);
}
tagTable = getTagTable();
} else {
setTagTable(tagTable);
}
startDocument("UTF-8", false);
} catch (IOException e) {
e.printStackTrace();
}
}
EASSerializer start (String tag) throws IOException {
startTag(null, tag);
return this;
}
EASSerializer end (String tag) throws IOException {
endTag(null, tag);
return this;
}
EASSerializer end () throws IOException {
endDocument();
return this;
}
EASSerializer data (String tag, String value) throws IOException {
startTag(null, tag);
text(value);
endTag(null, tag);
return this;
}
EASSerializer tag (String tag) throws IOException {
startTag(null, tag);
endTag(null, tag);
return this;
}
public EASSerializer text (String str) throws IOException {
super.text(str);
return this;
}
ByteArrayOutputStream getByteStream () {
return byteStream;
}
public String toString () {
return byteStream.toString();
}
}
public void runMain () {
try {
if (mAccount.mSyncKey == null) {
mAccount.mSyncKey = "0";
Log.w(TAG, "Account syncKey RESET");
mAccount.saveOrUpdate(mContext);
}
Log.v(TAG, "Account syncKey: " + mAccount.mSyncKey);
HttpURLConnection uc = setupEASCommand("OPTIONS", null);
if (uc != null) {
int code = uc.getResponseCode();
Log.v(TAG, "OPTIONS response: " + code);
if (code == HttpURLConnection.HTTP_OK) {
mVersions = uc.getHeaderField("ms-asprotocolversions");
if (mVersions != null) {
// Determine which version we want to use..
//List<String> versions = new Chain(mVersions, ',').toList();
//if (versions.contains("12.0")) {
// mProtocolVersion = "12.0";
//} else if (versions.contains("2.5"))
mProtocolVersion = "2.5";
Log.v(TAG, mVersions);
}
else {
String s = readResponseString(uc);
Log.e(TAG, "No EAS versions: " + s);
}
while (!mStop) {
EASSerializer s = new EASSerializer();
s.start("FolderSync").start("FolderSyncKey").text(mAccount.mSyncKey)
.end("FolderSyncKey").end("FolderSync").end();
String data = s.toString();
uc = sendEASPostCommand("FolderSync", data);
code = uc.getResponseCode();
Log.v(TAG, "FolderSync response code: " + code);
if (code == HttpURLConnection.HTTP_OK) {
String encoding = uc.getHeaderField("Transfer-Encoding");
if (encoding == null) {
int len = uc.getHeaderFieldInt("Content-Length", 0);
if (len > 0) {
try {
new EasFolderSyncParser(uc.getInputStream(), this).parse();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} else if (encoding.equalsIgnoreCase("chunked")) {
// TODO We don't handle this yet
}
}
// For now, we'll just loop
try {
Thread.sleep(15*MINS);
} catch (InterruptedException e) {
}
}
}
}
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
long handleLocalDeletes (EASSerializer s) throws IOException {
long maxDeleteId = -1;
// //**PROVIDER
// Cursor c = Email.getLocalDeletedCursor(mDatabase, mMailboxId);
// try {
// if (c.moveToFirst()) {
// s.start("Commands");
// mSentCommands = true;
// do {
// String serverId = c.getString(Email.MPN_UID_COLUMN);
// s.start("Delete").data("ServerId", serverId).end("Delete");
// mLogger.log("Sending delete of " + serverId);
// long id = c.getLong(Email.MPN_ID_COLUMN);
// if (id > maxDeleteId)
// maxDeleteId = id;
// } while (c.moveToNext());
// }
// } finally {
// c.close();
// }
return maxDeleteId;
}
void handleLocalMoves () throws IOException {
// long maxMoveId = -1;
//
// Cursor c = LocalChange.getCursorWhere(mDatabase, "mailbox=\"" + mMailbox.mServerId + "\" and type=" + LocalChange.MOVE_TYPE);
// try {
// if (c.moveToFirst()) {
// EASSerializer s = new EASSerializer();
// s.start("MoveItems");
//
// do {
// s.start("Move").data("SrcMsgId", c.getString(LocalChange.EMAIL_ID_COLUMN)).data("SrcFldId", c.getString(LocalChange.MAILBOX_COLUMN)).data("DstFldId", c.getString(LocalChange.VALUE_COLUMN)).end("Move");
// } while (c.moveToNext());
//
// s.end("MoveItems").end();
// HttpURLConnection uc = sendEASPostCommand("MoveItems", s.toString());
// int code = uc.getResponseCode();
// System.err.println("Response code: " + code);
// if (code == HttpURLConnection.HTTP_OK) {
// ByteArrayInputStream is = readResponse(uc);
// if (is != null) {
// EASMoveParser p = new EASMoveParser(is, this);
// p.parse();
// if (maxMoveId > -1)
// LocalChange.deleteWhere(mDatabase, "_id<=" + maxMoveId + " AND mailbox=" + mMailboxId + " AND type=" + LocalChange.MOVE_TYPE);
// }
// } else {
// // TODO What?
// }
// }
// } finally {
// c.close();
// }
}
long handleLocalReads (EASSerializer s) throws IOException {
Cursor c = mContext.getContentResolver().query(Message.CONTENT_URI, Message.LIST_PROJECTION, "mailboxKey=" + mMailboxId, null, null);
long maxReadId = -1;
try {
// if (c.moveToFirst()) {
// if (!mSentCommands) {
// s.start("Commands");
// mSentCommands = true;
// }
// do {
// String serverId = c.getString(LocalChange.STRING_ARGS_COLUMN);
// if (serverId == null) {
// long id = c.getInt(LocalChange.EMAIL_ID_COLUMN);
// Email.Message msg = Messages.restoreFromId(mContext, id);
// serverId = msg.serverId;
// if (serverId == null)
// serverId = "0:0";
// }
//
// String value = c.getString(LocalChange.VALUE_COLUMN);
// s.start("Change").data("ServerId", serverId).start("ApplicationData").data("Read", value).end("ApplicationData").end("Change");
// mLogger.log("Sending read of " + serverId + " = " + value);
// long id = c.getLong(LocalChange.ID_COLUMN);
// if (id > maxReadId)
// maxReadId = id;
// } while (c.moveToNext());
// }
} finally {
c.close();
}
return maxReadId;
//return -1;
}
ByteArrayInputStream readResponse (HttpURLConnection uc) throws IOException {
String encoding = uc.getHeaderField("Transfer-Encoding");
if (encoding == null) {
int len = uc.getHeaderFieldInt("Content-Length", 0);
if (len > 0) {
InputStream in = uc.getInputStream();
byte[] bytes = new byte[len];
int remain = len;
int offs = 0;
while (remain > 0) {
int read = in.read(bytes, offs, remain);
remain -= read;
offs += read;
}
return new ByteArrayInputStream(bytes);
}
} else if (encoding.equalsIgnoreCase("chunked")) {
// TODO We don't handle this yet
return null;
}
return null;
}
String readResponseString (HttpURLConnection uc) throws IOException {
String encoding = uc.getHeaderField("Transfer-Encoding");
if (encoding == null) {
int len = uc.getHeaderFieldInt("Content-Length", 0);
if (len > 0) {
InputStream in = uc.getInputStream();
byte[] bytes = new byte[len];
int remain = len;
int offs = 0;
while (remain > 0) {
int read = in.read(bytes, offs, remain);
remain -= read;
offs += read;
}
return new String(bytes);
}
} else if (encoding.equalsIgnoreCase("chunked")) {
// TODO We don't handle this yet
return null;
}
return null;
}
private String getSimulatedDeviceId () {
try {
File f = mContext.getFileStreamPath("deviceName");
BufferedReader rdr = null;
String id;
if (f.exists()) {
rdr = new BufferedReader(new FileReader(f));
id = rdr.readLine();
rdr.close();
return id;
} else if (f.createNewFile()) {
BufferedWriter w = new BufferedWriter(new FileWriter(f));
id = "emu" + System.currentTimeMillis();
w.write(id);
w.close();
}
} catch (FileNotFoundException e) {
} catch (IOException e) {
}
return null;
}
public void run() {
mThread = Thread.currentThread();
mDeviceId = android.provider.Settings.System
.getString(mContext.getContentResolver(), android.provider.Settings.System.ANDROID_ID);
HostAuth ha = HostAuth.restoreHostAuthWithId(mContext, mAccount.mHostAuthKeyRecv);
mHostAddress = ha.mAddress;
mUserName = ha.mLogin;
mPassword = ha.mPassword;
if (mDeviceId == null)
mDeviceId = getSimulatedDeviceId();
Log.v(TAG, "Device id: " + mDeviceId);
if (mMailbox.mServerId.equals("_main")) {
runMain();
return;
}
try {
while (!mStop) {
runAwake();
waitForConnectivity();
try {
// while (true) {
// PartRequest req = null;
// synchronized(mPartRequests) {
// if (mPartRequests.isEmpty()) {
// break;
// }
// req = mPartRequests.get(0);
// getAttachment(req);
// }
//
// synchronized(mPartRequests) {
// mPartRequests.remove(req);
// }
// }
boolean moreAvailable = true;
while (!mStop && moreAvailable) {
EASSerializer s = new EASSerializer();
if (mMailbox.mSyncKey == null) {
Log.w(TAG, "Mailbox syncKey RESET");
mMailbox.mSyncKey = "0";
}
Log.v(TAG, "Mailbox syncKey: " + mMailbox.mSyncKey);
s.start("Sync").start("Collections").start("Collection")
.data("Class", "Email")
.data("SyncKey", mMailbox.mSyncKey)
.data("CollectionId", mMailbox.mServerId);
// Set the lookback appropriately (EAS calls it a "filter")
String filter = FILTER_1_WEEK;
switch (mAccount.mSyncLookback) {
case Account.SYNC_WINDOW_1_DAY: {
filter = FILTER_1_DAY;
break;
}
case Account.SYNC_WINDOW_3_DAYS: {
filter = FILTER_3_DAYS;
break;
}
case Account.SYNC_WINDOW_1_WEEK: {
filter = FILTER_1_WEEK;
break;
}
case Account.SYNC_WINDOW_2_WEEKS: {
filter = FILTER_2_WEEKS;
break;
}
case Account.SYNC_WINDOW_1_MONTH: {
filter = FILTER_1_MONTH;
break;
}
case Account.SYNC_WINDOW_ALL: {
filter = FILTER_ALL;
break;
}
}
// For some crazy reason, GetChanges can't be used with a SyncKey of 0
if (!mMailbox.mSyncKey.equals("0")) {
if (mProtocolVersion.equals("12.0"))
s.tag("DeletesAsMoves")
.tag("GetChanges")
.data("WindowSize", WINDOW_SIZE)
.start("Options")
.data("FilterType", filter)
.start("BodyPreference")
.data("BodyPreferenceType", BODY_PREFERENCE_TEXT) // Plain text to start
.data("BodyPreferenceTruncationSize", "50000")
.end("BodyPreference")
.end("Options");
else
s.tag("DeletesAsMoves")
.tag("GetChanges")
.data("WindowSize", WINDOW_SIZE)
.start("Options")
.data("FilterType", filter)
.end("Options");
}
// Send our changes up to the server
mSentCommands = false;
// // Send local deletes to server
// long maxDeleteId = handleLocalDeletes(s);
// // Send local read changes
// long maxReadId = handleLocalReads(s);
if (mSentCommands) {
s.end("Commands");
}
s.end("Collection").end("Collections").end("Sync").end();
HttpURLConnection uc = sendEASPostCommand("Sync", s.toString());
int code = uc.getResponseCode();
Log.v(TAG, "Sync response code: " + code);
if (code == HttpURLConnection.HTTP_OK) {
ByteArrayInputStream is = readResponse(uc);
if (is != null) {
EasEmailSyncParser p = new EasEmailSyncParser(is, this);
p.parse();
// if (maxDeleteId > -1)
// Messages.deleteFromLocalDeletedWhere(mContext, "_id<=" + maxDeleteId);
// if (maxReadId > -1)
// LocalChange.deleteWhere(mDatabase, "_id<=" + maxReadId + " AND mailbox=" + mMailboxId + " AND type=" + LocalChange.READ_TYPE);
moreAvailable = p.mMoreAvailable;
}
} else {
// TODO What?
}
}
// Handle local moves
handleLocalMoves();
if (mMailbox.mSyncFrequency != Account.CHECK_INTERVAL_PUSH) {
return;
}
// Handle push here...
Thread pingThread = null;
EasPingService pingService = new EasPingService(mContext, mMailbox, this);
runAsleep(10*MINS);
synchronized (mWaitTarget) {
mIsIdle = true;
try {
log("Wait...");
pingThread = new Thread(pingService);
pingThread.setName("Ping " + pingThread.getId());
log("Starting thread " + pingThread.getName());
pingThread.start();
mWaitTarget.wait(14*MINS);
} catch (InterruptedException e) {
} finally {
runAwake();
}
log("Wait terminated.");
if (pingThread != null && pingThread.isAlive()) {
// Make the ping service stop, one way or another
log("Stopping " + pingThread.getName());
pingService.stop();
pingThread.interrupt();
}
mIsIdle = false;
}
} catch (IOException e) {
log("IOException: " + e.getMessage());
//logException(e);
}
}
} catch (Exception e) {
log("Exception: " + e.getMessage());
//logException(e);
} finally {
log("EAS sync finished.");
//MailService.done(this);
}
}
}

View File

@ -0,0 +1,769 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to 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 java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import javax.net.ssl.HttpsURLConnection;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
import org.apache.http.impl.client.DefaultHttpClient;
import com.android.email.mail.AuthenticationFailedException;
import com.android.email.mail.MessagingException;
import com.android.exchange.EmailContent.Account;
import com.android.exchange.EmailContent.Attachment;
import com.android.exchange.EmailContent.AttachmentColumns;
import com.android.exchange.EmailContent.HostAuth;
import com.android.exchange.EmailContent.Mailbox;
import com.android.exchange.EmailContent.MailboxColumns;
import com.android.exchange.adapter.EasContactsSyncAdapter;
import com.android.exchange.adapter.EasEmailSyncAdapter;
import com.android.exchange.adapter.EasFolderSyncParser;
import com.android.exchange.adapter.EasPingParser;
import com.android.exchange.adapter.EasSerializer;
import com.android.exchange.adapter.EasSyncAdapter;
import com.android.exchange.adapter.EasParser.EasParserException;
import com.android.exchange.utility.Base64;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.util.Log;
public class EasSyncService extends InteractiveSyncService {
private static final String WINDOW_SIZE = "10";
private static final String WHERE_ACCOUNT_KEY_AND_SERVER_ID =
MailboxColumns.ACCOUNT_KEY + "=? and " + MailboxColumns.SERVER_ID + "=?";
private static final String WHERE_SYNC_FREQUENCY_PING =
Mailbox.SYNC_FREQUENCY + '=' + Account.CHECK_INTERVAL_PING;
private static final String SYNC_FREQUENCY_PING =
MailboxColumns.SYNC_FREQUENCY + '=' + Account.CHECK_INTERVAL_PING;
// Reasonable default
String mProtocolVersion = "2.5";
static String mDeviceId = null;
static String mDeviceType = "Android";
EasSyncAdapter mTarget;
String mAuthString = null;
String mCmdString = null;
String mVersions;
public String mHostAddress;
public String mUserName;
public String mPassword;
String mDomain = null;
boolean mSentCommands;
boolean mIsIdle = false;
boolean mSsl = true;
public Context mContext;
public ContentResolver mContentResolver;
String[] mBindArguments = new String[2];
InputStream mPendingPartInputStream = null;
private boolean mStop = false;
private Object mWaitTarget = new Object();
public EasSyncService(Context _context, Mailbox _mailbox) {
super(_context, _mailbox);
mContext = _context;
mContentResolver = _context.getContentResolver();
HostAuth ha = HostAuth.restoreHostAuthWithId(_context, mAccount.mHostAuthKeyRecv);
mSsl = (ha.mFlags & HostAuth.FLAG_SSL) != 0;
}
private EasSyncService(String prefix) {
super(prefix);
}
public EasSyncService() {
this("EAS Validation");
}
@Override
public void ping() {
userLog("We've been pinged!");
synchronized (mWaitTarget) {
mWaitTarget.notify();
}
}
@Override
public void stop() {
mStop = true;
}
public int getSyncStatus() {
return 0;
}
/* (non-Javadoc)
* @see com.android.exchange.SyncService#validateAccount(java.lang.String, java.lang.String, java.lang.String, int, boolean, android.content.Context)
*/
public void validateAccount(String hostAddress, String userName, String password, int port,
boolean ssl, Context context) throws MessagingException {
try {
if (Eas.USER_DEBUG) {
userLog("Testing EAS: " + hostAddress + ", " + userName + ", ssl = " + ssl);
}
EasSerializer s = new EasSerializer();
s.start("FolderSync").start("FolderSyncKey").text("0").end("FolderSyncKey")
.end("FolderSync").end();
EasSyncService svc = new EasSyncService("%TestAccount%");
svc.mHostAddress = hostAddress;
svc.mUserName = userName;
svc.mPassword = password;
svc.mSsl = ssl;
HttpURLConnection uc = svc.sendEASPostCommand("FolderSync", s.toString());
int code = uc.getResponseCode();
userLog("Validation response code: " + code);
if (code == HttpURLConnection.HTTP_OK) {
// No exception means successful validation
userLog("Validation successful");
return;
}
if (code == HttpURLConnection.HTTP_UNAUTHORIZED ||
code == HttpURLConnection.HTTP_FORBIDDEN) {
userLog("Authentication failed");
throw new AuthenticationFailedException("Validation failed");
} else {
// TODO Need to catch other kinds of errors (e.g. policy) For now, report the code.
userLog("Validation failed, reporting I/O error: " + code);
throw new MessagingException(MessagingException.IOERROR);
}
} catch (IOException e) {
userLog("IOException caught, reporting I/O error: " + e.getMessage());
throw new MessagingException(MessagingException.IOERROR);
}
}
@Override
public void loadAttachment(Attachment att, ISyncManagerCallback cb) {
// TODO Auto-generated method stub
}
@Override
public void reloadFolderList() {
// TODO Auto-generated method stub
}
@Override
public void startSync() {
// TODO Auto-generated method stub
}
@Override
public void stopSync() {
// TODO Auto-generated method stub
}
protected HttpURLConnection sendEASPostCommand(String cmd, String data) throws IOException {
HttpURLConnection uc = setupEASCommand("POST", cmd);
if (uc != null) {
uc.setRequestProperty("Content-Length", Integer.toString(data.length() + 2));
OutputStreamWriter w = new OutputStreamWriter(uc.getOutputStream(), "UTF-8");
w.write(data);
w.write("\r\n");
w.flush();
w.close();
}
return uc;
}
static private final int CHUNK_SIZE = 16 * 1024;
protected void getAttachment(PartRequest req) throws IOException {
DefaultHttpClient client = new DefaultHttpClient();
String us = makeUriString("GetAttachment", "&AttachmentName=" + req.att.mLocation);
HttpPost method = new HttpPost(URI.create(us));
method.setHeader("Authorization", mAuthString);
HttpResponse res = client.execute(method);
int status = res.getStatusLine().getStatusCode();
if (status == HttpURLConnection.HTTP_OK) {
HttpEntity e = res.getEntity();
int len = (int)e.getContentLength();
String type = e.getContentType().getValue();
if (Eas.TEST_DEBUG) {
Log.v(TAG, "Attachment code: " + status + ", Length: " + len + ", Type: " + type);
}
InputStream is = res.getEntity().getContent();
// TODO Use the request data, when it's defined. For now, stubbed out
File f = null; // Attachment.openAttachmentFile(req);
if (f != null) {
FileOutputStream os = new FileOutputStream(f);
if (len > 0) {
try {
mPendingPartRequest = req;
mPendingPartInputStream = is;
byte[] bytes = new byte[CHUNK_SIZE];
int length = len;
while (len > 0) {
int n = (len > CHUNK_SIZE ? CHUNK_SIZE : len);
int read = is.read(bytes, 0, n);
os.write(bytes, 0, read);
len -= read;
if (req.handler != null) {
long pct = ((length - len) * 100 / length);
req.handler.sendEmptyMessage((int)pct);
}
}
} finally {
mPendingPartRequest = null;
mPendingPartInputStream = null;
}
}
os.flush();
os.close();
ContentValues cv = new ContentValues();
cv.put(AttachmentColumns.CONTENT_URI, f.getAbsolutePath());
cv.put(AttachmentColumns.MIME_TYPE, type);
req.att.update(mContext, cv);
// TODO Inform UI that we're done
}
}
}
private HttpURLConnection setupEASCommand(String method, String cmd) throws IOException {
return setupEASCommand(method, cmd, null);
}
private String makeUriString(String cmd, String extra) {
// Cache the authentication string and the command string
if (mDeviceId == null)
mDeviceId = "droidfu";
String safeUserName = URLEncoder.encode(mUserName);
if (mAuthString == null) {
String cs = mUserName + ':' + mPassword;
mAuthString = "Basic " + Base64.encodeBytes(cs.getBytes());
mCmdString = "&User=" + safeUserName + "&DeviceId=" + mDeviceId + "&DeviceType="
+ mDeviceType;
}
String us = (mSsl ? "https" : "http") + "://" + mHostAddress +
"/Microsoft-Server-ActiveSync";
if (cmd != null) {
us += "?Cmd=" + cmd + mCmdString;
}
if (extra != null) {
us += extra;
}
return us;
}
private HttpURLConnection setupEASCommand(String method, String cmd, String extra)
throws IOException {
try {
String us = makeUriString(cmd, extra);
URL u = new URL(us);
HttpURLConnection uc = (HttpURLConnection)u.openConnection();
HttpURLConnection.setFollowRedirects(true);
if (mSsl) {
((HttpsURLConnection)uc).setHostnameVerifier(new AllowAllHostnameVerifier());
}
uc.setConnectTimeout(10 * SECS);
uc.setReadTimeout(20 * MINS);
if (method.equals("POST")) {
uc.setDoOutput(true);
}
uc.setRequestMethod(method);
uc.setRequestProperty("Authorization", mAuthString);
if (extra == null) {
if (cmd != null && cmd.startsWith("SendMail&")) {
uc.setRequestProperty("Content-Type", "message/rfc822");
} else {
uc.setRequestProperty("Content-Type", "application/vnd.ms-sync.wbxml");
}
uc.setRequestProperty("MS-ASProtocolVersion", mProtocolVersion);
uc.setRequestProperty("Connection", "keep-alive");
uc.setRequestProperty("User-Agent", mDeviceType + '/' + Eas.VERSION);
} else {
uc.setRequestProperty("Content-Length", "0");
}
return uc;
} catch (MalformedURLException e) {
// TODO See if there is a better exception to throw here and below
throw new IOException();
} catch (ProtocolException e) {
throw new IOException();
}
}
String getTargetCollectionClassFromCursor(Cursor c) {
int type = c.getInt(Mailbox.CONTENT_TYPE_COLUMN);
if (type == Mailbox.TYPE_CONTACTS) {
return "Contacts";
} else if (type == Mailbox.TYPE_CALENDAR) {
return "Calendar";
} else {
return "Email";
}
}
/**
* Performs FolderSync
*
* @throws IOException
* @throws EasParserException
*/
public void runMain() throws IOException, EasParserException {
try {
if (mAccount.mSyncKey == null) {
mAccount.mSyncKey = "0";
userLog("Account syncKey RESET");
mAccount.saveOrUpdate(mContext);
}
// When we first start up, change all ping mailboxes to push.
ContentValues cv = new ContentValues();
cv.put(Mailbox.SYNC_FREQUENCY, Account.CHECK_INTERVAL_PUSH);
if (mContentResolver.update(Mailbox.CONTENT_URI, cv,
WHERE_SYNC_FREQUENCY_PING, null) > 0) {
SyncManager.kick();
}
userLog("Account syncKey: " + mAccount.mSyncKey);
HttpURLConnection uc = setupEASCommand("OPTIONS", null);
if (uc != null) {
int code = uc.getResponseCode();
userLog("OPTIONS response: " + code);
if (code == HttpURLConnection.HTTP_OK) {
mVersions = uc.getHeaderField("ms-asprotocolversions");
if (mVersions != null) {
if (mVersions.contains("12.0")) {
mProtocolVersion = "12.0";
}
// TODO We only do 2.5 at the moment; add 'else' above when fixed
mProtocolVersion = "2.5";
userLog(mVersions);
} else {
throw new IOException();
}
while (!mStop) {
EasSerializer s = new EasSerializer();
s.start("FolderSync").start("FolderSyncKey").text(mAccount.mSyncKey).end(
"FolderSyncKey").end("FolderSync").end();
uc = sendEASPostCommand("FolderSync", s.toString());
code = uc.getResponseCode();
if (code == HttpURLConnection.HTTP_OK) {
String encoding = uc.getHeaderField("Transfer-Encoding");
if (encoding == null) {
int len = uc.getHeaderFieldInt("Content-Length", 0);
if (len > 0) {
InputStream is = uc.getInputStream();
// Returns true if we need to sync again
if (new EasFolderSyncParser(is, this).parse()) {
continue;
}
}
} else if (encoding.equalsIgnoreCase("chunked")) {
// TODO We don't handle this yet
}
} else {
userLog("FolderSync response error: " + code);
}
// Wait for push notifications.
try {
runPingLoop();
} catch (StaleFolderListException e) {
// We break out if we get told about a stale folder list
userLog("Ping interrupted; folder list requires sync...");
}
}
}
}
} catch (MalformedURLException e) {
throw new IOException();
}
}
void runPingLoop() throws IOException, StaleFolderListException {
// Do push for all sync services here
long endTime = System.currentTimeMillis() + (30*MINS);
while (System.currentTimeMillis() < endTime) {
// Count of pushable mailboxes
int pushCount = 0;
// Count of mailboxes that can be pushed right now
int canPushCount = 0;
EasSerializer s = new EasSerializer();
HttpURLConnection uc;
int code;
Cursor c = mContentResolver.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION,
MailboxColumns.ACCOUNT_KEY + '=' + mAccount.mId + " and " + SYNC_FREQUENCY_PING,
null, null);
try {
// Loop through our pushed boxes seeing what is available to push
while (c.moveToNext()) {
pushCount++;
// Two requirements for push:
// 1) SyncManager tells us the mailbox is syncable (not running, not stopped)
// 2) The syncKey isn't "0" (i.e. it's synced at least once)
if (SyncManager.canSync(c.getLong(Mailbox.CONTENT_ID_COLUMN))) {
String syncKey = c.getString(Mailbox.CONTENT_SYNC_KEY_COLUMN);
if (syncKey == null || syncKey.equals("0")) {
continue;
}
if (canPushCount++ == 0) {
// Initialize the Ping command
s.start("Ping").data("HeartbeatInterval", "900").start("PingFolders");
}
// When we're ready for Calendar/Contacts, we will check folder type
// TODO Save Calendar and Contacts!! Mark as not visible!
String folderClass = getTargetCollectionClassFromCursor(c);
s.start("PingFolder")
.data("PingId", c.getString(Mailbox.CONTENT_SERVER_ID_COLUMN))
.data("PingClass", folderClass)
.end("PingFolder");
}
}
} finally {
c.close();
}
if (canPushCount > 0) {
// If we have some number that are ready for push, send Ping to the server
s.end("PingFolders").end("Ping").end();
uc = sendEASPostCommand("Ping", s.toString());
userLog("Sending ping, timeout: " + uc.getReadTimeout() / 1000 + "s");
code = uc.getResponseCode();
userLog("Ping response: " + code);
if (code == HttpURLConnection.HTTP_OK) {
String encoding = uc.getHeaderField("Transfer-Encoding");
if (encoding == null) {
int len = uc.getHeaderFieldInt("Content-Length", 0);
if (len > 0) {
parsePingResult(uc, mContentResolver);
} else {
// This implies a connection issue that we can't handle
throw new IOException();
}
} else {
// It shouldn't be possible for EAS server to send chunked data here
throw new IOException();
}
} else if (code == HttpURLConnection.HTTP_UNAUTHORIZED ||
code == HttpURLConnection.HTTP_FORBIDDEN) {
mExitStatus = AbstractSyncService.EXIT_LOGIN_FAILURE;
userLog("Authorization error during Ping: " + code);
throw new IOException();
}
} else if (pushCount > 0) {
// If we want to Ping, but can't just yet, wait 10 seconds and try again
sleep(10*SECS);
} else {
// We've got nothing to do, so let's hang out for a while
sleep(10*MINS);
}
}
}
void sleep(long ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
// Doesn't matter whether we stop early; it's the thought that counts
}
}
void parsePingResult(HttpURLConnection uc, ContentResolver cr)
throws IOException, StaleFolderListException {
EasPingParser pp = new EasPingParser(uc.getInputStream(), this);
if (pp.parse()) {
// True indicates some mailboxes need syncing...
// syncList has the serverId's of the mailboxes...
mBindArguments[0] = Long.toString(mAccount.mId);
ArrayList<String> syncList = pp.getSyncList();
for (String serverId: syncList) {
mBindArguments[1] = serverId;
Cursor c = cr.query(Mailbox.CONTENT_URI, Mailbox.CONTENT_PROJECTION,
WHERE_ACCOUNT_KEY_AND_SERVER_ID, mBindArguments, null);
try {
if (c.moveToFirst()) {
SyncManager.startManualSync(c.getLong(Mailbox.CONTENT_ID_COLUMN));
}
} finally {
c.close();
}
}
}
}
ByteArrayInputStream readResponse(HttpURLConnection uc) throws IOException {
String encoding = uc.getHeaderField("Transfer-Encoding");
if (encoding == null) {
int len = uc.getHeaderFieldInt("Content-Length", 0);
if (len > 0) {
InputStream in = uc.getInputStream();
byte[] bytes = new byte[len];
int remain = len;
int offs = 0;
while (remain > 0) {
int read = in.read(bytes, offs, remain);
remain -= read;
offs += read;
}
return new ByteArrayInputStream(bytes);
}
} else if (encoding.equalsIgnoreCase("chunked")) {
// TODO We don't handle this yet
return null;
}
return null;
}
String readResponseString(HttpURLConnection uc) throws IOException {
String encoding = uc.getHeaderField("Transfer-Encoding");
if (encoding == null) {
int len = uc.getHeaderFieldInt("Content-Length", 0);
if (len > 0) {
InputStream in = uc.getInputStream();
byte[] bytes = new byte[len];
int remain = len;
int offs = 0;
while (remain > 0) {
int read = in.read(bytes, offs, remain);
remain -= read;
offs += read;
}
return new String(bytes);
}
} else if (encoding.equalsIgnoreCase("chunked")) {
// TODO We don't handle this yet
return null;
}
return null;
}
/**
* EAS requires a unique device id, so that sync is possible from a variety of different
* devices (e.g. the syncKey is specific to a device) If we're on an emulator or some other
* device that doesn't provide one, we can create it as droid<n> where <n> is system time.
* This would work on a real device as well, but it would be better to use the "real" id if
* it's available
*/
private String getSimulatedDeviceId() {
try {
File f = mContext.getFileStreamPath("deviceName");
BufferedReader rdr = null;
String id;
if (f.exists() && f.canRead()) {
rdr = new BufferedReader(new FileReader(f));
id = rdr.readLine();
rdr.close();
return id;
} else if (f.createNewFile()) {
BufferedWriter w = new BufferedWriter(new FileWriter(f));
id = "droid" + System.currentTimeMillis();
w.write(id);
w.close();
}
} catch (FileNotFoundException e) {
// We'll just use the default below
} catch (IOException e) {
// We'll just use the default below
}
return "droid0";
}
/**
* Common code to sync E+PIM data
*
* @param target, an EasMailbox, EasContacts, or EasCalendar object
*/
public void sync(EasSyncAdapter target) throws IOException {
mTarget = target;
Mailbox mailbox = target.mMailbox;
boolean moreAvailable = true;
while (!mStop && moreAvailable) {
runAwake();
waitForConnectivity();
EasSerializer s = new EasSerializer();
if (mailbox.mSyncKey == null) {
userLog("Mailbox syncKey RESET");
mailbox.mSyncKey = "0";
mailbox.mSyncFrequency = Account.CHECK_INTERVAL_PUSH;
}
String className = target.getCollectionName();
userLog("Sending " + className + " syncKey: " + mailbox.mSyncKey);
s.start("Sync")
.start("Collections")
.start("Collection")
.data("Class", className)
.data("SyncKey", mailbox.mSyncKey)
.data("CollectionId", mailbox.mServerId)
.tag("DeletesAsMoves");
// EAS doesn't like GetChanges if the syncKey is "0"; not documented
if (!mailbox.mSyncKey.equals("0")) {
s.tag("GetChanges");
}
s.data("WindowSize", WINDOW_SIZE);
boolean options = false;
if (!className.equals("Contacts")) {
options = true;
// Set the lookback appropriately (EAS calls this a "filter")
String filter = Eas.FILTER_1_WEEK;
switch (mAccount.mSyncLookback) {
case com.android.email.Account.SYNC_WINDOW_1_DAY: {
filter = Eas.FILTER_1_DAY;
break;
}
case com.android.email.Account.SYNC_WINDOW_3_DAYS: {
filter = Eas.FILTER_3_DAYS;
break;
}
case com.android.email.Account.SYNC_WINDOW_1_WEEK: {
filter = Eas.FILTER_1_WEEK;
break;
}
case com.android.email.Account.SYNC_WINDOW_2_WEEKS: {
filter = Eas.FILTER_2_WEEKS;
break;
}
case com.android.email.Account.SYNC_WINDOW_1_MONTH: {
filter = Eas.FILTER_1_MONTH;
break;
}
case com.android.email.Account.SYNC_WINDOW_ALL: {
filter = Eas.FILTER_ALL;
break;
}
}
s.start("Options")
.data("FilterType", filter);
}
if (mProtocolVersion.equals("12.0")) {
if (!options) {
options = true;
s.start("Options");
s.start("BodyPreference")
// Plain text to start
.data("BodyPreferenceType", Eas.BODY_PREFERENCE_TEXT)
.data("BodyPreferenceTruncationSize", Eas.DEFAULT_BODY_TRUNCATION_SIZE)
.end("BodyPreference");
}
}
if (options) {
s.end("Options");
}
// Send our changes up to the server
target.sendLocalChanges(s, this);
s.end("Collection").end("Collections").end("Sync").end();
HttpURLConnection uc = sendEASPostCommand("Sync", s.toString());
int code = uc.getResponseCode();
if (code == HttpURLConnection.HTTP_OK) {
ByteArrayInputStream is = readResponse(uc);
if (is != null) {
moreAvailable = target.parse(is, this);
}
} else {
userLog("Sync response error: " + code);
if (code == HttpURLConnection.HTTP_UNAUTHORIZED ||
code == HttpURLConnection.HTTP_FORBIDDEN) {
mExitStatus = AbstractSyncService.EXIT_LOGIN_FAILURE;
}
return;
}
}
}
/* (non-Javadoc)
* @see java.lang.Runnable#run()
*/
public void run() {
mThread = Thread.currentThread();
TAG = mThread.getName();
mDeviceId = android.provider.Settings.System.getString(mContext.getContentResolver(),
android.provider.Settings.System.ANDROID_ID);
// Generate a device id if we don't have one
if (mDeviceId == null) {
mDeviceId = getSimulatedDeviceId();
}
HostAuth ha = HostAuth.restoreHostAuthWithId(mContext, mAccount.mHostAuthKeyRecv);
mHostAddress = ha.mAddress;
mUserName = ha.mLogin;
mPassword = ha.mPassword;
try {
if (mMailbox.mServerId.equals("_main")) {
runMain();
} else {
EasSyncAdapter target;
if (mMailbox.mType == Mailbox.TYPE_CONTACTS)
target = new EasContactsSyncAdapter(mMailbox);
else {
target = new EasEmailSyncAdapter(mMailbox);
}
// We loop here because someone might have put a request in while we were syncing
// and we've missed that opportunity...
do {
if (mRequestTime != 0) {
userLog("Looping for user request...");
mRequestTime = 0;
}
sync(target);
} while (mRequestTime != 0);
}
mExitStatus = EXIT_DONE;
} catch (IOException e) {
userLog("Caught IOException");
mExitStatus = EXIT_IO_ERROR;
} catch (Exception e) {
e.printStackTrace();
} finally {
userLog(mMailbox.mDisplayName + ": sync finished");
SyncManager.done(this);
}
}
}

View File

@ -1,345 +0,0 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to 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;
public class EasTags {
static final int AIRSYNC = 0x00;
static final int CONTACTS = 0x01;
static final int EMAIL = 0x02;
static final int FOLDER = 0x07;
static final int PING = 0x0D;
static final int GAL = 0x10;
static final int SYNC_SYNC = 5;
static final int SYNC_RESPONSES = 6;
static final int SYNC_ADD = 7;
static final int SYNC_CHANGE = 8;
static final int SYNC_DELETE = 9;
static final int SYNC_FETCH = 0xA;
static final int SYNC_SYNC_KEY = 0xB;
static final int SYNC_CLIENT_ID = 0xC;
static final int SYNC_SERVER_ID = 0xD;
static final int SYNC_STATUS = 0xE;
static final int SYNC_COLLECTION = 0xF;
static final int SYNC_CLASS = 0x10;
static final int SYNC_VERSION = 0x11;
static final int SYNC_COLLECTION_ID = 0x12;
static final int SYNC_GET_CHANGES = 0x13;
static final int SYNC_MORE_AVAILABLE = 0x14;
static final int SYNC_WINDOW_SIZE = 0x15;
static final int SYNC_COMMANDS = 0x16;
static final int SYNC_OPTIONS = 0x17;
static final int SYNC_FILTER_TYPE = 0x18;
static final int SYNC_TRUNCATION = 0x19;
static final int SYNC_RTF_TRUNCATION = 0x1A;
static final int SYNC_CONFLICT = 0x1B;
static final int SYNC_COLLECTIONS = 0x1C;
static final int SYNC_APPLICATION_DATA = 0x1D;
static final int SYNC_DELETES_AS_MOVES = 0x1E;
static final int SYNC_NOTIFY_GUID = 0x1F;
static final int SYNC_SUPPORTED = 0x20;
static final int SYNC_SOFT_DELETE = 0x21;
static final int SYNC_MIME_SUPPORT = 0x22;
static final int SYNC_MIME_TRUNCATION = 0x23;
static final int SYNC_WAIT = 0x24;
static final int SYNC_LIMIT = 0x25;
static final int SYNC_PARTIAL = 0x26;
static final int CALENDAR_TIME_ZONE = 5;
static final int CALENDAR_ALL_DAY_EVENT = 6;
static final int CALENDAR_ATTENDEES = 7;
static final int CALENDAR_ATTENDEE = 8;
static final int CALENDAR_ATTENDEE_EMAIL = 9;
static final int CALENDAR_ATTENDEE_NAME = 0xA;
static final int CALENDAR_BODY = 0xB;
static final int CALENDAR_BODY_TRUNCATED = 0xC;
static final int CALENDAR_BUSY_STATUS = 0xD;
static final int CALENDAR_CATEGORIES = 0xE;
static final int CALENDAR_CATEGORY = 0xF;
static final int CALENDAR_COMPRESSED_RTF = 0x10;
static final int CALENDAR_DTSTAMP = 0x11;
static final int CALENDAR_END_TIME = 0x12;
static final int CALENDAR_EXCEPTION = 0x13;
static final int CALENDAR_EXCEPTIONS = 0x14;
static final int CALENDAR_EXCEPTION_IS_DELETED = 0x15;
static final int CALENDAR_EXCEPTION_START_TIME = 0x16;
static final int CALENDAR_LOCATION = 0x17;
static final int CALENDAR_MEETING_STATUS = 0x18;
static final int CALENDAR_ORGANIZER_EMAIL = 0x19;
static final int CALENDAR_ORGANIZER_NAME = 0x1A;
static final int CALENDAR_RECURRENCE = 0x1B;
static final int CALENDAR_RECURRENCE_TYPE = 0x1C;
static final int CALENDAR_RECURRENCE_UNTIL = 0x1D;
static final int CALENDAR_RECURRENCE_OCCURRENCES = 0x1E;
static final int CALENDAR_RECURRENCE_INTERVAL = 0x1F;
static final int CALENDAR_RECURRENCE_DAYOFWEEK = 0x20;
static final int CALENDAR_RECURRENCE_DAYOFMONTH = 0x21;
static final int CALENDAR_RECURRENCE_WEEKOFMONTH = 0x22;
static final int CALENDAR_RECURRENCE_MONTHOFYEAR = 0x23;
static final int CALENDAR_REMINDER_MINS_BEFORE = 0x24;
static final int CALENDAR_SENSITIVITY = 0x25;
static final int CALENDAR_SUBJECT = 0x26;
static final int CALENDAR_START_TIME = 0x27;
static final int CALENDAR_UID = 0x28;
static final int CALENDAR_ATTENDEE_STATUS = 0x29;
static final int CALENDAR_ATTENDEE_TYPE = 0x2A;
static final int FOLDER_FOLDERS = 5;
static final int FOLDER_FOLDER = 6;
static final int FOLDER_DISPLAY_NAME = 7;
static final int FOLDER_SERVER_ID = 8;
static final int FOLDER_PARENT_ID = 9;
static final int FOLDER_TYPE = 0xA;
static final int FOLDER_RESPONSE = 0xB;
static final int FOLDER_STATUS = 0xC;
static final int FOLDER_CONTENT_CLASS = 0xD;
static final int FOLDER_CHANGES = 0xE;
static final int FOLDER_ADD = 0xF;
static final int FOLDER_DELETE = 0x10;
static final int FOLDER_UPDATE = 0x11;
static final int FOLDER_SYNC_KEY = 0x12;
static final int FOLDER_FOLDER_CREATE = 0x13;
static final int FOLDER_FOLDER_DELETE= 0x14;
static final int FOLDER_FOLDER_UPDATE = 0x15;
static final int FOLDER_FOLDER_SYNC = 0x16;
static final int FOLDER_COUNT = 0x17;
static final int FOLDER_VERSION = 0x18;
static final int EMAIL_ATTACHMENT = 5;
static final int EMAIL_ATTACHMENTS = 6;
static final int EMAIL_ATT_NAME = 7;
static final int EMAIL_ATT_SIZE = 8;
static final int EMAIL_ATT0ID = 9;
static final int EMAIL_ATT_METHOD = 0xA;
static final int EMAIL_ATT_REMOVED = 0xB;
static final int EMAIL_BODY = 0xC;
static final int EMAIL_BODY_SIZE = 0xD;
static final int EMAIL_BODY_TRUNCATED = 0xE;
static final int EMAIL_DATE_RECEIVED = 0xF;
static final int EMAIL_DISPLAY_NAME = 0x10;
static final int EMAIL_DISPLAY_TO = 0x11;
static final int EMAIL_IMPORTANCE = 0x12;
static final int EMAIL_MESSAGE_CLASS = 0x13;
static final int EMAIL_SUBJECT = 0x14;
static final int EMAIL_READ = 0x15;
static final int EMAIL_TO = 0x16;
static final int EMAIL_CC = 0x17;
static final int EMAIL_FROM = 0x18;
static final int EMAIL_REPLY_TO = 0x19;
static final int EMAIL_ALL_DAY_EVENT = 0x1A;
static final int EMAIL_CATEGORIES = 0x1B;
static final int EMAIL_CATEGORY = 0x1C;
static final int EMAIL_DTSTAMP = 0x1D;
static final int EMAIL_END_TIME = 0x1E;
static final int EMAIL_INSTANCE_TYPE = 0x1F;
static final int EMAIL_INTD_BUSY_STATUS = 0x20;
static final int EMAIL_LOCATION = 0x21;
static final int EMAIL_MEETING_REQUEST = 0x22;
static final int EMAIL_ORGANIZER = 0x23;
static final int EMAIL_RECURRENCE_ID = 0x24;
static final int EMAIL_REMINDER = 0x25;
static final int EMAIL_RESPONSE_REQUESTED = 0x26;
static final int EMAIL_RECURRENCES = 0x27;
static final int EMAIL_RECURRENCE = 0x28;
static final int EMAIL_RECURRENCE_TYPE = 0x29;
static final int EMAIL_RECURRENCE_UNTIL = 0x2A;
static final int EMAIL_RECURRENCE_OCCURRENCES = 0x2B;
static final int EMAIL_RECURRENCE_INTERVAL = 0x2C;
static final int EMAIL_RECURRENCE_DAYOFWEEK = 0x2D;
static final int EMAIL_RECURRENCE_DAYOFMONTH = 0x2E;
static final int EMAIL_RECURRENCE_WEEKOFMONTH = 0x2F;
static final int EMAIL_RECURRENCE_MONTHOFYEAR = 0x30;
static final int EMAIL_START_TIME = 0x31;
static final int EMAIL_SENSITIVITY = 0x32;
static final int EMAIL_TIME_ZONE = 0x33;
static final int EMAIL_GLOBAL_OBJID = 0x34;
static final int EMAIL_THREAD_TOPIC = 0x35;
static final int EMAIL_MIME_DATA = 0x36;
static final int EMAIL_MIME_TRUNCATED = 0x37;
static final int EMAIL_MIME_SIZE = 0x38;
static final int EMAIL_INTERNET_CPID = 0x39;
static final int EMAIL_FLAG = 0x3A;
static final int EMAIL_FLAG_STATUS = 0x3B;
static final int EMAIL_CONTENT_CLASS = 0x3C;
static final int EMAIL_FLAG_TYPE = 0x3D;
static final int EMAIL_COMPLETE_TIME = 0x3E;
static final int MOVE_MOVE_ITEMS = 5;
static final int MOVE_MOVE = 6;
static final int MOVE_SRCMSGID = 7;
static final int MOVE_SRCFLDID = 8;
static final int MOVE_DSTFLDID = 9;
static final int MOVE_RESPONSE = 0xA;
static final int MOVE_STATUS = 0xB;
static final int MOVE_DSTMSGID = 0xC;
static final int PING_PING = 5;
static final int PING_AUTD_STATE = 6;
static final int PING_STATUS = 7;
static final int PING_HEARTBEAT_INTERVAL = 8;
static final int PING_FOLDERS = 9;
static final int PING_FOLDER = 0xA;
static final int PING_ID = 0xB;
static final int PING_CLASS = 0xC;
static final int PING_MAX_FOLDERS = 0xD;
static final int BASE_BODY_PREFERENCE = 5;
static final int BASE_TYPE = 6;
static final int BASE_TRUNCATION_SIZE = 7;
static final int BASE_ALL_OR_NONE = 8;
static final int BASE_RESERVED = 9;
static final int BASE_BODY = 0xA;
static final int BASE_DATA = 0xB;
static final int BASE_ESTIMATED_DATA_SIZE = 0xC;
static final int BASE_TRUNCATED = 0xD;
static final int BASE_ATTACHMENTS = 0xE;
static final int BASE_ATTACHMENT = 0xF;
static final int BASE_DISPLAY_NAME = 0x10;
static final int BASE_FILE_REFERENCE = 0x11;
static final int BASE_METHOD = 0x12;
static final int BASE_CONTENT_ID = 0x13;
static final int BASE_CONTENT_LOCATION = 0x14;
static final int BASE_IS_INLINE = 0x15;
static final int BASE_NATIVE_BODY_TYPE = 0x16;
static final int BASE_CONTENT_TYPE = 0x17;
static public String[][] pages = {
{ // 0x00 AirSync
"Sync", "Responses", "Add", "Change", "Delete", "Fetch", "SyncKey", "ClientId",
"ServerId", "Status", "Collection", "Class", "Version", "CollectionId", "GetChanges",
"MoreAvailable", "WindowSize", "Commands", "Options", "FilterType", "Truncation",
"RTFTruncation", "Conflict", "Collections", "ApplicationData", "DeletesAsMoves",
"NotifyGUID", "Supported", "SoftDelete", "MIMESupport", "MIMETruncation", "Wait",
"Limit", "Partial"
},
{
// 0x01 Contacts
},
{
// 0x02 Email
"Attachment", "Attachments", "AttName", "AttSize", "Add0Id", "AttMethod", "AttRemoved",
"Body", "BodySize", "BodyTruncated", "DateReceived", "DisplayName", "DisplayTo",
"Importance", "MessageClass", "Subject", "Read", "To", "CC", "From", "ReplyTo",
"AllDayEvent", "Categories", "Category", "DTStamp", "EndTime", "InstanceType",
"IntDBusyStatus", "Location", "MeetingRequest", "Organizer", "RecurrenceId", "Reminder",
"ResponseRequested", "Recurrences", "Recurence", "Recurrence_Type", "Recurrence_Until",
"Recurrence_Occurrences", "Recurrence_Interval", "Recurrence_DayOfWeek",
"Recurrence_DayOfMonth", "Recurrence_WeekOfMonth", "Recurrence_MonthOfYear",
"StartTime", "Sensitivity", "TimeZone", "GlobalObjId", "ThreadTopic", "MIMEData",
"MIMETruncated", "MIMESize", "InternetCPID", "Flag", "FlagStatus", "ContentClass",
"FlagType", "CompleteTime"
},
{
// 0x03 AirNotify
},
{
// 0x04 Calendar
"CalTimeZone", "CalAllDayEvent", "CalAttendees", "CalAttendee", "CalAttendee_Email",
"CalAttendee_Name", "CalBody", "CalBodyTruncated", "CalBusyStatus", "CalCategories",
"CalCategory", "CalCompressed_RTF", "CalDTStamp", "CalEndTime", "CalExeption",
"CalExceptions", "CalException_IsDeleted", "CalException_StartTime", "CalLocation",
"CalMeetingStatus", "CalOrganizer_Email", "CalOrganizer_Name", "CalRecurrence",
"CalRecurrence_Type", "CalRecurrence_Until", "CalRecurrence_Occurrences",
"CalRecurrence_Interval", "CalRecurrence_DayOfWeek", "CalRecurrence_DayOfMonth",
"CalRecurrence_WeekOfMonth", "CalRecurrence_MonthOfYear", "CalReminder_MinsBefore",
"CalSensitivity", "CalSubject", "CalStartTime", "CalUID", "CalAttendee_Status",
"CalAttendee_Type"
},
{
// 0x05 Move
"MoveItems", "Move", "SrcMsgId", "SrcFldId", "DstFldId", "Response", "Status",
"DstMsgId"
},
{
// 0x06 ItemEstimate
},
{
// 0x07 FolderHierarchy
"Folders", "Folder", "FolderDisplayName", "FolderServerId", "FolderParentId", "Type",
"Response", "Status", "ContentClass", "Changes", "FolderAdd", "FolderDelete",
"FolderUpdate", "FolderSyncKey", "FolderCreate", "FolderDelete", "FolderUpdate",
"FolderSync", "Count", "Version"
},
{
// 0x08 MeetingResponse
},
{
// 0x09 Tasks
},
{
// 0x0A ResolveRecipients
},
{
// 0x0B ValidateCert
},
{
// 0x0C Contacts2
},
{
// 0x0D Ping
"Ping", "AutdState", "Status", "HeartbeatInterval", "PingFolders", "PingFolder",
"PingId", "PingClass", "MaxFolders"
},
{
// 0x0E Provision
"Provision", "Policies", "Policy", "PolicyType", "PolicyKey", "Data", "Status",
"RemoteWipe", "EASProvidionDoc", "DevicePasswordEnabled",
"AlphanumericDevicePasswordRequired",
"DeviceEncryptionEnabled", "-unused-", "AttachmentsEnabled", "MinDevicePasswordLength",
"MaxInactivityTimeDeviceLock", "MaxDevicePasswordFailedAttempts", "MaxAttachmentSize",
"AllowSimpleDevicePassword", "DevicePasswordExpiration", "DevicePasswordHistory",
"AllowStorageCard", "AllowCamera", "RequireDeviceEncryption",
"AllowUnsignedApplications", "AllowUnsignedInstallationPackages",
"MinDevicePasswordComplexCharacters", "AllowWiFi", "AllowTextMessaging",
"AllowPOPIMAPEmail", "AllowBluetooth", "AllowIrDA", "RequireManualSyncWhenRoaming",
"AllowDesktopSync",
"MaxCalendarAgeFilder", "AllowHTMLEmail", "MaxEmailAgeFilder",
"MaxEmailBodyTruncationSize", "MaxEmailHTMLBodyTruncationSize",
"RequireSignedSMIMEMessages", "RequireEncryptedSMIMEMessages",
"RequireSignedSMIMEAlgorithm", "RequireEncryptionSMIMEAlgorithm",
"AllowSMIMEEncryptionAlgorithmNegotiation", "AllowSMIMESoftCerts", "AllowBrowser",
"AllowConsumerEmail", "AllowRemoteDesktop", "AllowInternetSharing",
"UnapprovedInROMApplicationList", "ApplicationName", "ApprovedApplicationList", "Hash"
},
{
// 0x0F Search
},
{
// 0x10 Gal
"DisplayName", "Phone", "Office", "Title", "Company", "Alias", "FirstName", "LastName",
"HomePhone", "MobilePhone", "EmailAddress"
},
{
// 0x11 AirSyncBase
"BodyPreference", "BodyPreferenceType", "BodyPreferenceTruncationSize", "AllOrNone",
"Body", "Data", "EstimatedDataSize", "Truncated", "Attachments", "Attachment",
"DisplayName", "FileReference", "Method", "ContentId", "ContentLocation", "IsInline",
"NativeBodyType", "ContentType"
},
{
// 0x12 Settings
},
{
// 0x13 DocumentLibrary
},
{
// 0x14 ItemOperations
}
};
}

View File

@ -14,6 +14,12 @@
* limitations under the License.
*/
/**
* This is a local copy of com.android.email.EmailProvider
*
* Last copied from com.android.email.EmailProvider on 7/2/09
*/
package com.android.exchange;
import com.android.email.R;
@ -223,10 +229,7 @@ public abstract class EmailContent {
public String mHtmlContent;
public String mTextContent;
/**
* no public constructor since this is a utility class
*/
private Body() {
public Body() {
mBaseUri = CONTENT_URI;
}
@ -283,7 +286,7 @@ public abstract class EmailContent {
@Override
@SuppressWarnings("unchecked")
public EmailContent.Body restore(Cursor c) {
mBaseUri = EmailContent.Message.CONTENT_URI;
mBaseUri = EmailContent.Body.CONTENT_URI;
mMessageKey = c.getLong(CONTENT_MESSAGE_KEY_COLUMN);
mHtmlContent = c.getString(CONTENT_HTML_CONTENT_COLUMN);
mTextContent = c.getString(CONTENT_TEXT_CONTENT_COLUMN);
@ -352,8 +355,17 @@ public abstract class EmailContent {
public static final class Message extends EmailContent implements SyncColumns, MessageColumns {
public static final String TABLE_NAME = "Message";
public static final String UPDATES_TABLE_NAME = "Message_Updates";
public static final String UPDATED_TABLE_NAME = "Message_Updates";
public static final String DELETED_TABLE_NAME = "Message_Deletes";
// To refer to a specific message, use ContentUris.withAppendedId(CONTENT_URI, id)
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/message");
public static final Uri SYNCED_CONTENT_URI =
Uri.parse(EmailContent.CONTENT_URI + "/syncedMessage");
public static final Uri DELETED_CONTENT_URI =
Uri.parse(EmailContent.CONTENT_URI + "/deletedMessage");
public static final Uri UPDATED_CONTENT_URI =
Uri.parse(EmailContent.CONTENT_URI + "/updatedMessage");
public static final String KEY_TIMESTAMP_DESC = MessageColumns.TIMESTAMP + " desc";
@ -489,9 +501,6 @@ public abstract class EmailContent {
mBaseUri = CONTENT_URI;
}
public static final Uri UPDATED_CONTENT_URI =
Uri.parse(EmailContent.CONTENT_URI + "/updatedMessage");
@Override
public ContentValues toContentValues() {
ContentValues values = new ContentValues();
@ -500,6 +509,7 @@ public abstract class EmailContent {
values.put(MessageColumns.DISPLAY_NAME, mDisplayName);
values.put(MessageColumns.TIMESTAMP, mTimeStamp);
values.put(MessageColumns.SUBJECT, mSubject);
values.put(MessageColumns.PREVIEW, mPreview);
values.put(MessageColumns.FLAG_READ, mFlagRead);
values.put(MessageColumns.FLAG_LOADED, mFlagLoaded);
values.put(MessageColumns.FLAG_FAVORITE, mFlagFavorite);
@ -517,6 +527,7 @@ public abstract class EmailContent {
values.put(MessageColumns.CLIENT_ID, mClientId);
values.put(MessageColumns.MESSAGE_ID, mMessageId);
values.put(MessageColumns.THREAD_ID, mThreadId);
values.put(MessageColumns.MAILBOX_KEY, mMailboxKey);
values.put(MessageColumns.ACCOUNT_KEY, mAccountKey);
@ -744,6 +755,7 @@ public abstract class EmailContent {
public static final int CHECK_INTERVAL_NEVER = -1;
public static final int CHECK_INTERVAL_PUSH = -2;
public static final int CHECK_INTERVAL_PING = -3;
public static final int SYNC_WINDOW_USER = -1;
@ -1653,6 +1665,12 @@ public abstract class EmailContent {
// Holds junk mail
public static final int TYPE_JUNK = 7;
// Types after this are used for non-mail mailboxes (as in EAS)
public static final int TYPE_NOT_EMAIL = 0x40;
public static final int TYPE_CALENDAR = 0x41;
public static final int TYPE_CONTACTS = 0x42;
public static final int TYPE_TASKS = 0x43;
// Bit field flags
public static final int FLAG_HAS_CHILDREN = 1<<0;
public static final int FLAG_CHILDREN_VISIBLE = 1<<1;
@ -1746,9 +1764,9 @@ public abstract class EmailContent {
public static final String TABLE_NAME = "HostAuth";
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/hostauth");
private static final int FLAG_SSL = 1;
private static final int FLAG_TLS = 2;
private static final int FLAG_AUTHENTICATE = 4;
public static final int FLAG_SSL = 1;
public static final int FLAG_TLS = 2;
public static final int FLAG_AUTHENTICATE = 4;
public String mProtocol;
public String mAddress;

View File

@ -1,24 +0,0 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to 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 java.io.IOException;
public class EofException extends IOException {
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,50 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to 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 android.content.Context;
import com.android.exchange.EmailContent.Attachment;
import com.android.exchange.EmailContent.Mailbox;
/**
* The parent class of all SyncServices that are interactive (i.e. need to
* respond to user input in a timely way. The abstract methods for the most part
* track the service methods available in the ISyncManager interface. The
* SyncManager is responsible for ensuring that an InteractiveSyncService has
* been started, and then passes the appropriate call into it. Each ISS will
* interpret/handle the method as it deems appropriate.
*/
public abstract class InteractiveSyncService extends AbstractSyncService {
public InteractiveSyncService(Context _context, Mailbox _mailbox) {
super(_context, _mailbox);
}
public InteractiveSyncService(String prefix) {
super(prefix);
}
public abstract void startSync();
public abstract void stopSync();
public abstract void reloadFolderList();
public abstract void loadAttachment(Attachment att, ISyncManagerCallback cb);
}

View File

@ -1,6 +1,5 @@
/*
/*
* Copyright (C) 2008-2009 Marc Blank
* Copyright (C) 2008-2009 Marc Blank
* Licensed to The Android Open Source Project.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -22,15 +21,19 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class KeepAliveReceiver extends BroadcastReceiver {
/**
* MailboxAlarmReceiver is used to "wake up" the SyncManager at the appropriate time(s). It may
* also be used for individual sync adapters, but this isn't implemented at the present time.
*
*/
public class MailboxAlarmReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
long mid = intent.getLongExtra("mailbox", -1);
if (mid < 0) {
SyncManager.kick();
}
else {
SyncManager.ping(mid);
if (SyncManager.INSTANCE != null) {
SyncManager.INSTANCE.log("Alarm received for: " + mid);
}
SyncManager.ping(mid);
}
}

View File

@ -1,243 +0,0 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to 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 java.util.ArrayList;
import com.android.email.Email;
import com.android.email.mail.MessagingException;
import com.android.exchange.EmailContent.Account;
import com.android.exchange.EmailContent.Mailbox;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.util.Log;
// Base class for all protocol services
// Some common functionality is included here; note that each protocol will implement run() individually
// MailService (extends Service, implements Runnable) instantiates subclasses when it's time to run a sync
// (either timed, or push, or mail placed in outbox, etc.)
// Current subclasses are IMAPService, EASService, and SMTPService, with POP3Service to come...
public abstract class ProtocolService implements Runnable {
public static final String TAG = "ProtocolService";
public static final String SUMMARY_PROTOCOL = "_SUMMARY_";
public static final String SYNCED_PROTOCOL = "_SYNCING_";
public static final String MOVE_FAVORITES_PROTOCOL = "_MOVE_FAVORITES_";
public static final int CONNECT_TIMEOUT = 30000;
public static final int NETWORK_WAIT = 15000;
public static final int SECS = 1000;
public static final int MINS = 60*SECS;
public static final int HRS = 60*MINS;
public static final int DAYS = 24*HRS;
public static final String IMAP_PROTOCOL = "imap";
public static final String EAS_PROTOCOL = "eas";
// Making SSL connections is so slow that I'd prefer that only one be executed at a time
// Kindly subclasses will synchronize on this before making an SSL connection
public static Object sslGovernorToken = new Object();
protected Mailbox mMailbox;
protected long mMailboxId;
protected Thread mThread;
protected String mMailboxName;
protected Account mAccount;
protected Context mContext;
protected long mRequestTime;
protected ArrayList<PartRequest> mPartRequests = new ArrayList<PartRequest>();
protected PartRequest mPendingPartRequest = null;
// Stop is sent by the MailService to request that the service stop itself cleanly. An example
// would be for the implementation of sleep hours
public abstract void stop ();
// Ping is sent by the MailService to indicate that a user request requiring service has been added to
// request queue; response is service dependent
public abstract void ping ();
// MailService calls this to determine the sync state of the protocol service. By default,
// this is "SYNC", but it might, for example, be "IDLE" (i.e. push), in which case the method will be
// overridden. Could be abstract, but ... nah.
public int getSyncStatus() {
return 0;
//return MailService.SyncStatus.SYNC;
}
public ProtocolService (Context _context, Mailbox _mailbox) {
mContext = _context;
mMailbox = _mailbox;
mMailboxId = _mailbox.mId;
mMailboxName = _mailbox.mServerId;
mAccount = Account.restoreAccountWithId(_context, _mailbox.mAccountKey);
}
// Will be required when subclasses are instantiated by name
public ProtocolService (String prefix) {
}
public abstract void validateAccount (String host, String userName, String password,
int port, boolean ssl, Context context) throws MessagingException;
static public void validate (Class<? extends ProtocolService> klass, String host,
String userName, String password, int port, boolean ssl, Context context)
throws MessagingException {
ProtocolService svc;
try {
svc = klass.newInstance();
svc.validateAccount(host, userName, password, port, ssl, context);
} catch (IllegalAccessException e) {
throw new MessagingException("internal error", e);
} catch (InstantiationException e) {
throw new MessagingException("internal error", e);
}
}
public static class ValidationResult {
static final int NO_FAILURE = 0;
static final int CONNECTION_FAILURE = 1;
static final int VALIDATION_FAILURE = 2;
static final int EXCEPTION = 3;
static final ValidationResult succeeded = new ValidationResult(true, NO_FAILURE, null);
boolean success;
int failure = NO_FAILURE;
String reason = null;
Exception exception = null;
ValidationResult (boolean _success, int _failure, String _reason) {
success = _success;
failure = _failure;
reason = _reason;
}
ValidationResult (boolean _success) {
success = _success;
}
ValidationResult (Exception e) {
success = false;
failure = EXCEPTION;
exception = e;
}
public boolean isSuccess () {
return success;
}
public String getReason () {
return reason;
}
}
public final void runAwake () {
//MailService.runAwake(mMailboxId);
}
public final void runAsleep (long millis) {
//MailService.runAsleep(mMailboxId, millis);
}
// Common call used by the various protocols to send a "mail" message to the UI
protected void updateUI () {
}
protected void log (String str) {
if (Email.DEBUG) {
Log.v(Email.LOG_TAG, str);
}
}
// Delay until there is some kind of network connectivity
// Subclasses should allow some number of retries before failing, and kicking the ball back to MailService
public int waitForConnectivity () {
ConnectivityManager cm = (ConnectivityManager)mContext
.getSystemService(Context.CONNECTIVITY_SERVICE);
while (true) {
NetworkInfo info = cm.getActiveNetworkInfo();
if (info != null && info.isConnected()) {
DetailedState state = info.getDetailedState();
if (state == DetailedState.CONNECTED) {
return info.getType();
} else {
// TODO Happens sometimes; find out why...
log("Not quite connected? Pause 1 second");
}
pause(1000);
} else {
log("Not connected; waiting 15 seconds");
pause(NETWORK_WAIT);
}
}
}
// Convenience
private void pause (int ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
}
}
// PartRequest handling (common functionality)
// Can be overridden if desired, but IMAP/EAS both use the next three methods as-is
public void addPartRequest (PartRequest req) {
synchronized(mPartRequests) {
mPartRequests.add(req);
}
}
public void removePartRequest (PartRequest req) {
synchronized(mPartRequests) {
mPartRequests.remove(req);
}
}
public PartRequest hasPartRequest(long emailId, String part) {
synchronized(mPartRequests) {
for (PartRequest pr: mPartRequests) {
if (pr.emailId == emailId && pr.loc.equals(part))
return pr;
}
}
return null;
}
// CancelPartRequest is sent in response to user input to stop a request (attachment load at this point)
// that is in progress. This will almost certainly require code overriding the base functionality, as
// sockets may need to be closed, etc. and this functionality will be service dependent. This returns
// the canceled PartRequest or null
public PartRequest cancelPartRequest(long emailId, String part) {
synchronized(mPartRequests) {
PartRequest p = null;
for (PartRequest pr: mPartRequests) {
if (pr.emailId == emailId && pr.loc.equals(part)) {
p = pr;
break;
}
}
if (p != null) {
mPartRequests.remove(p);
return p;
}
}
return null;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Copyright (C) 2008-2009 Marc Blank
* Licensed to The Android Open Source Project.
*
* Licensed under the Apache License, Version 2.0 (the "License");
@ -17,6 +17,6 @@
package com.android.exchange;
public class EasParserException extends Exception {
public class StaleFolderListException extends EasException {
private static final long serialVersionUID = 1L;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,98 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to 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 java.util.ArrayList;
import com.android.exchange.EmailContent.Message;
import com.android.exchange.EmailContent.MessageColumns;
import com.android.exchange.EmailContent.SyncColumns;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.util.Log;
/**
* UserSyncAlarmReceiver (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:
*
* 1) User modifies or deletes an email from the UI.
* 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
* changes to be uploaded.
* 6) USAR calls SyncManager to start syncs of those mailboxes
*
*/
public class UserSyncAlarmReceiver extends BroadcastReceiver {
final String[] MAILBOX_DATA_PROJECTION = {MessageColumns.MAILBOX_KEY, SyncColumns.DATA};
private static String TAG = "UserSyncAlarm";
@Override
public void onReceive(Context context, Intent intent) {
Log.v(TAG, "onReceive");
ArrayList<Long> mailboxesToNotify = new ArrayList<Long>();
ContentResolver cr = context.getContentResolver();
int messageCount = 0;
// Find all of the deletions
Cursor c = cr.query(Message.DELETED_CONTENT_URI, MAILBOX_DATA_PROJECTION,
null, null, null);
try {
// Keep track of which mailboxes to notify; we'll only notify each one once
while (c.moveToNext()) {
messageCount++;
long mailboxId = c.getLong(0);
if (!mailboxesToNotify.contains(mailboxId)) {
mailboxesToNotify.add(mailboxId);
}
}
} finally {
c.close();
}
// Now, find changed messages
c = cr.query(Message.UPDATED_CONTENT_URI, MAILBOX_DATA_PROJECTION,
null, null, null);
try {
// Keep track of which mailboxes to notify; we'll only notify each one once
while (c.moveToNext()) {
messageCount++;
long mailboxId = c.getLong(0);
if (!mailboxesToNotify.contains(mailboxId)) {
mailboxesToNotify.add(mailboxId);
}
}
} finally {
c.close();
}
// Request service from the mailbox
for (Long mailboxId: mailboxesToNotify) {
SyncManager.serviceRequest(mailboxId);
}
Log.v(TAG, "Changed/Deleted messages: " + messageCount + ", mailboxes: " +
mailboxesToNotify.size());
}
}

View File

@ -0,0 +1,52 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to 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.adapter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import com.android.exchange.EasSyncService;
import com.android.exchange.EmailContent.Mailbox;
/**
* Sync adapter class for EAS calendars
*
*/
public class EasCalendarSyncAdapter extends EasSyncAdapter {
public EasCalendarSyncAdapter(Mailbox mailbox) {
super(mailbox);
}
@Override
public boolean parse(ByteArrayInputStream is, EasSyncService service) throws IOException {
// TODO Auto-generated method stub
return false;
}
@Override
public String getCollectionName() {
return "Calendar";
}
@Override
public boolean sendLocalChanges(EasSerializer s, EasSyncService service) throws IOException {
// TODO Auto-generated method stub
return false;
}
}

View File

@ -0,0 +1,385 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to 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.adapter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import android.content.ContentProviderOperation;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.provider.Contacts;
import android.provider.Contacts.People;
import com.android.exchange.EasSyncService;
import com.android.exchange.EmailContent.Mailbox;
/**
* Sync adapter for EAS Contacts
*
*/
public class EasContactsSyncAdapter extends EasSyncAdapter {
private static final String WHERE_SERVER_ID_AND_ACCOUNT = "_sync_id=?";
ArrayList<Long> mDeletedIdList = new ArrayList<Long>();
ArrayList<Long> mUpdatedIdList = new ArrayList<Long>();
public EasContactsSyncAdapter(Mailbox mailbox) {
super(mailbox);
}
@Override
public boolean parse(ByteArrayInputStream is, EasSyncService service) throws IOException {
EasContactsSyncParser p = new EasContactsSyncParser(is, service);
return p.parse();
}
class EasContactsSyncParser extends EasContentParser {
String[] mBindArgument = new String[1];
String mMailboxIdAsString;
StringBuilder mExtraData = new StringBuilder(1024);
public EasContactsSyncParser(InputStream in, EasSyncService service) throws IOException {
super(in, service);
//setDebug(true); // DON'T CHECK IN WITH THIS UNCOMMENTED
}
class ContactMethod {
ContentValues values = new ContentValues();
ContactMethod(int kind, int type, String value) {
values.put(Contacts.ContactMethods.KIND, kind);
values.put(Contacts.ContactMethods.TYPE, type);
values.put(Contacts.ContactMethods.DATA, value);
}
}
class Phone {
ContentValues values = new ContentValues();
Phone(int type, String value) {
values.put(Contacts.Phones.TYPE, type);
values.put(Contacts.Phones.NUMBER, value);
}
}
@Override
public void wipe() {
// TODO Auto-generated method stub
}
void saveExtraData (int tag) throws IOException {
mExtraData.append(name);
mExtraData.append("~");
mExtraData.append(getValue());
mExtraData.append('~');
}
public void addData(String serverId, ArrayList<ContentProviderOperation> ops)
throws IOException {
String firstName = null;
String lastName = null;
String companyName = null;
ArrayList<ContactMethod> contactMethods = new ArrayList<ContactMethod>();
ArrayList<Phone> phones = new ArrayList<Phone>();
while (nextTag(EasTags.SYNC_APPLICATION_DATA) != END) {
switch (tag) {
case EasTags.CONTACTS_FIRST_NAME:
firstName = getValue();
break;
case EasTags.CONTACTS_LAST_NAME:
lastName = getValue();
break;
case EasTags.CONTACTS_COMPANY_NAME:
companyName = getValue();
break;
case EasTags.CONTACTS_EMAIL1_ADDRESS:
case EasTags.CONTACTS_EMAIL2_ADDRESS:
case EasTags.CONTACTS_EMAIL3_ADDRESS:
contactMethods.add(new ContactMethod(Contacts.KIND_EMAIL,
Contacts.ContactMethods.TYPE_OTHER, getValue()));
break;
case EasTags.CONTACTS_BUSINESS2_TELEPHONE_NUMBER:
case EasTags.CONTACTS_BUSINESS_TELEPHONE_NUMBER:
phones.add(new Phone(Contacts.Phones.TYPE_WORK, getValue()));
break;
case EasTags.CONTACTS_BUSINESS_FAX_NUMBER:
phones.add(new Phone(Contacts.Phones.TYPE_FAX_WORK, getValue()));
break;
case EasTags.CONTACTS_HOME_FAX_NUMBER:
phones.add(new Phone(Contacts.Phones.TYPE_FAX_HOME, getValue()));
break;
case EasTags.CONTACTS_HOME_TELEPHONE_NUMBER:
case EasTags.CONTACTS_HOME2_TELEPHONE_NUMBER:
phones.add(new Phone(Contacts.Phones.TYPE_HOME, getValue()));
break;
case EasTags.CONTACTS_MOBILE_TELEPHONE_NUMBER:
case EasTags.CONTACTS_CAR_TELEPHONE_NUMBER:
phones.add(new Phone(Contacts.Phones.TYPE_MOBILE, getValue()));
break;
case EasTags.CONTACTS_PAGER_NUMBER:
phones.add(new Phone(Contacts.Phones.TYPE_PAGER, getValue()));
break;
// All tags that we don't use (except for a few like picture and body) need to
// be saved, even if we're not using them. Otherwise, when we upload changes,
// those items will be deleted back on the server.
case EasTags.CONTACTS_ANNIVERSARY:
case EasTags.CONTACTS_ASSISTANT_NAME:
case EasTags.CONTACTS_ASSISTANT_TELEPHONE_NUMBER:
case EasTags.CONTACTS_BIRTHDAY:
case EasTags.CONTACTS_BUSINESS_ADDRESS_CITY:
case EasTags.CONTACTS_BUSINESS_ADDRESS_COUNTRY:
case EasTags.CONTACTS_BUSINESS_ADDRESS_POSTAL_CODE:
case EasTags.CONTACTS_BUSINESS_ADDRESS_STATE:
case EasTags.CONTACTS_BUSINESS_ADDRESS_STREET:
case EasTags.CONTACTS_CATEGORIES:
case EasTags.CONTACTS_CATEGORY:
case EasTags.CONTACTS_CHILDREN:
case EasTags.CONTACTS_CHILD:
case EasTags.CONTACTS_DEPARTMENT:
case EasTags.CONTACTS_FILE_AS:
case EasTags.CONTACTS_HOME_ADDRESS_CITY:
case EasTags.CONTACTS_HOME_ADDRESS_COUNTRY:
case EasTags.CONTACTS_HOME_ADDRESS_POSTAL_CODE:
case EasTags.CONTACTS_HOME_ADDRESS_STATE:
case EasTags.CONTACTS_HOME_ADDRESS_STREET:
case EasTags.CONTACTS_JOB_TITLE:
case EasTags.CONTACTS_MIDDLE_NAME:
case EasTags.CONTACTS_OFFICE_LOCATION:
case EasTags.CONTACTS_OTHER_ADDRESS_CITY:
case EasTags.CONTACTS_OTHER_ADDRESS_COUNTRY:
case EasTags.CONTACTS_OTHER_ADDRESS_POSTAL_CODE:
case EasTags.CONTACTS_OTHER_ADDRESS_STATE:
case EasTags.CONTACTS_OTHER_ADDRESS_STREET:
case EasTags.CONTACTS_RADIO_TELEPHONE_NUMBER:
case EasTags.CONTACTS_SPOUSE:
case EasTags.CONTACTS_SUFFIX:
case EasTags.CONTACTS_TITLE:
case EasTags.CONTACTS_WEBPAGE:
case EasTags.CONTACTS_YOMI_COMPANY_NAME:
case EasTags.CONTACTS_YOMI_FIRST_NAME:
case EasTags.CONTACTS_YOMI_LAST_NAME:
case EasTags.CONTACTS_COMPRESSED_RTF:
//case EasTags.CONTACTS_PICTURE:
case EasTags.CONTACTS2_CUSTOMER_ID:
case EasTags.CONTACTS2_GOVERNMENT_ID:
case EasTags.CONTACTS2_IM_ADDRESS:
case EasTags.CONTACTS2_IM_ADDRESS_2:
case EasTags.CONTACTS2_IM_ADDRESS_3:
case EasTags.CONTACTS2_MANAGER_NAME:
case EasTags.CONTACTS2_COMPANY_MAIN_PHONE:
case EasTags.CONTACTS2_ACCOUNT_NAME:
case EasTags.CONTACTS2_NICKNAME:
case EasTags.CONTACTS2_MMS:
saveExtraData(tag);
break;
default:
skipTag();
}
}
// Ok, ready to create our contact...
// First pass, no batch... Eventually, move to changesParser
ContentValues values = new ContentValues();
// TODO Do something with the extras (i.e. find a home for them)
String extraData = mExtraData.toString();
mService.userLog(extraData);
// We must have first name, last name, or company name
String name;
if (firstName != null || lastName != null) {
if (firstName == null) {
name = lastName;
} else if (lastName == null) {
name = firstName;
} else {
name = firstName + ' ' + lastName;
}
} else if (companyName != null) {
name = companyName;
} else {
return;
}
values.put(Contacts.People.NAME, name);
values.put("_sync_id", serverId);
// TODO Use proper value here; need to ask jham
//values.put("_sync_account", "EAS");
Uri contactUri =
Contacts.People.createPersonInMyContactsGroup(mContentResolver, values);
Uri contactMethodsUri = Uri.withAppendedPath(contactUri,
Contacts.People.ContactMethods.CONTENT_DIRECTORY);
for (ContactMethod cm: contactMethods) {
mContentResolver.insert(contactMethodsUri, cm.values);
//ops.add(ContentProviderOperation
// .newInsert(contactMethodsUri).withValues(cm.values).build());
}
Uri phoneUri = Uri.withAppendedPath(contactUri, People.Phones.CONTENT_DIRECTORY);
for (Phone phone: phones) {
mContentResolver.insert(phoneUri, phone.values);
//ops.add(ContentProviderOperation
// .newInsert(phoneUri).withValues(phone.values).build());
}
}
public void addParser(ArrayList<ContentProviderOperation> ops) throws IOException {
String serverId = null;
while (nextTag(EasTags.SYNC_ADD) != END) {
switch (tag) {
case EasTags.SYNC_SERVER_ID: // same as
serverId = getValue();
break;
case EasTags.SYNC_APPLICATION_DATA:
addData(serverId, ops);
break;
default:
skipTag();
}
}
}
private Cursor getServerIdCursor(String serverId) {
mBindArgument[0] = serverId;
//bindArguments[1] = "EAS";
// TODO Find proper constant for _id
return mContentResolver.query(Contacts.People.CONTENT_URI, new String[] {"_id"},
WHERE_SERVER_ID_AND_ACCOUNT, mBindArgument, null);
}
public void deleteParser(ArrayList<ContentProviderOperation> ops) throws IOException {
while (nextTag(EasTags.SYNC_DELETE) != END) {
switch (tag) {
case EasTags.SYNC_SERVER_ID:
String serverId = getValue();
// Find the message in this mailbox with the given serverId
Cursor c = getServerIdCursor(serverId);
try {
if (c.moveToFirst()) {
mService.userLog("Deleting " + serverId);
mContentResolver.delete(ContentUris
.withAppendedId(Contacts.People.CONTENT_URI, c.getLong(0)),
null, null);
//ops.add(ContentProviderOperation.newDelete(
// ContentUris.withAppendedId(Contacts.People.CONTENT_URI,
// c.getLong(0))).build());
}
} finally {
c.close();
}
break;
default:
skipTag();
}
}
}
class ServerChange {
long id;
boolean read;
ServerChange(long _id, boolean _read) {
id = _id;
read = _read;
}
}
/**
* A change operation on a contact is implemented as a delete followed by an add, since the
* change data is always a full contact.
*
* @param ops the array of pending ContactProviderOperations.
* @throws IOException
*/
public void changeParser(ArrayList<ContentProviderOperation> ops) throws IOException {
String serverId = null;
while (nextTag(EasTags.SYNC_CHANGE) != END) {
switch (tag) {
case EasTags.SYNC_SERVER_ID:
serverId = getValue();
Cursor c = getServerIdCursor(serverId);
try {
if (c.moveToFirst()) {
mContentResolver.delete(ContentUris
.withAppendedId(Contacts.People.CONTENT_URI, c.getLong(0)),
null, null);
//ops.add(ContentProviderOperation.newDelete(
// ContentUris.withAppendedId(Contacts.People.CONTENT_URI,
// c.getLong(0))).build());
mService.userLog("Changing " + serverId);
}
} finally {
c.close();
}
break;
case EasTags.SYNC_APPLICATION_DATA:
addData(serverId, ops);
default:
skipTag();
}
}
}
public void commandsParser() throws IOException {
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
while (nextTag(EasTags.SYNC_COMMANDS) != END) {
if (tag == EasTags.SYNC_ADD) {
addParser(ops);
} else if (tag == EasTags.SYNC_DELETE) {
deleteParser(ops);
} else if (tag == EasTags.SYNC_CHANGE) {
changeParser(ops);
} else
skipTag();
}
// Batch provider operations here
// try {
// mService.mContext.getContentResolver()
// .applyBatch(ContactsProvider.EMAIL_AUTHORITY, ops);
// } catch (RemoteException e) {
// // There is nothing to be done here; fail by returning null
// } catch (OperationApplicationException e) {
// // There is nothing to be done here; fail by returning null
// }
mService.userLog("SyncKey confirmed as: " + mMailbox.mSyncKey);
}
}
@Override
public String getCollectionName() {
return "Contacts";
}
@Override
public boolean sendLocalChanges(EasSerializer s, EasSyncService service) throws IOException {
return false;
}
}

View File

@ -0,0 +1,141 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to 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.adapter;
import java.io.IOException;
import java.io.InputStream;
import android.content.ContentResolver;
import android.content.Context;
import com.android.exchange.EasSyncService;
import com.android.exchange.EmailContent.Account;
import com.android.exchange.EmailContent.Mailbox;
/**
* Base class for the Email and PIM sync parsers
* Handles the basic flow of syncKeys, looping to get more data, handling errors, etc.
* Each subclass must implement a handful of methods that relate specifically to the data type
*
*/
public abstract class EasContentParser extends EasParser {
EasSyncService mService;
Mailbox mMailbox;
Account mAccount;
Context mContext;
ContentResolver mContentResolver;
public EasContentParser(InputStream in, EasSyncService _service) throws IOException {
super(in);
mService = _service;
mContext = mService.mContext;
mContentResolver = mContext.getContentResolver();
mMailbox = mService.mMailbox;
mAccount = mService.mAccount;
}
/**
* Read, parse, and act on incoming commands from the Exchange server
* @throws IOException if the connection is broken
*/
public abstract void commandsParser() throws IOException;
/**
* Read, parse, and act on server responses
* Email doesn't have any, so this isn't yet implemented anywhere. It will become abstract,
* in the near future, however.
* @throws IOException
*/
public void responsesParser() throws IOException {
// Placeholder until needed; will become an abstract method
}
/**
* Delete all records of this class in this account
*/
public abstract void wipe();
/**
* Loop through the top-level structure coming from the Exchange server
* Sync keys and the more available flag are handled here, whereas specific data parsing
* is handled by abstract methods implemented for each data class (e.g. Email, Contacts, etc.)
*/
public boolean parse() throws IOException {
int status;
boolean moreAvailable = false;
// If we're not at the top of the xml tree, throw an exception
if (nextTag(START_DOCUMENT) != EasTags.SYNC_SYNC) {
throw new IOException();
}
// Loop here through the remaining xml
while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
if (tag == EasTags.SYNC_COLLECTION || tag == EasTags.SYNC_COLLECTIONS) {
// Ignore these tags, since we've only got one collection syncing in this loop
} else if (tag == EasTags.SYNC_STATUS) {
// Status = 1 is success; everything else is a failure
status = getValueInt();
if (status != 1) {
mService.errorLog("Sync failed: " + status);
// Status = 3 means invalid sync key
if (status == 3) {
// Must delete all of the data and start over with syncKey of "0"
mMailbox.mSyncKey = "0";
// Make this a push box through the first sync
// TODO Make frequency conditional on user settings!
mMailbox.mSyncFrequency = Account.CHECK_INTERVAL_PUSH;
mService.errorLog("Bad sync key; RESET and delete contacts");
wipe();
// Indicate there's more so that we'll start syncing again
moreAvailable = true;
}
}
} else if (tag == EasTags.SYNC_COMMANDS) {
commandsParser();
} else if (tag == EasTags.SYNC_RESPONSES) {
responsesParser();
} else if (tag == EasTags.SYNC_MORE_AVAILABLE) {
moreAvailable = true;
} else if (tag == EasTags.SYNC_SYNC_KEY) {
if (mMailbox.mSyncKey.equals("0"))
moreAvailable = true;
String newKey = getValue();
mService.userLog("New sync key: " + newKey);
mMailbox.mSyncKey = newKey;
// If we were pushing (i.e. auto-start), now we'll become ping-triggered
if (mMailbox.mSyncFrequency == Account.CHECK_INTERVAL_PUSH) {
mMailbox.mSyncFrequency = Account.CHECK_INTERVAL_PING;
}
} else {
skipTag();
}
}
// Make sure we save away the new syncKey, syncFrequency, etc.
mMailbox.saveOrUpdate(mContext);
// Let the caller know that there's more to do
return moreAvailable;
}
}

View File

@ -0,0 +1,436 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to 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.adapter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.TimeZone;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.os.RemoteException;
import com.android.email.provider.EmailProvider;
import com.android.exchange.EasSyncService;
import com.android.exchange.EmailContent.Attachment;
import com.android.exchange.EmailContent.Mailbox;
import com.android.exchange.EmailContent.Message;
import com.android.exchange.EmailContent.MessageColumns;
import com.android.exchange.EmailContent.SyncColumns;
/**
* Sync adapter for EAS email
*
*/
public class EasEmailSyncAdapter extends EasSyncAdapter {
private static final String[] UPDATES_PROJECTION = {MessageColumns.FLAG_READ};
ArrayList<Long> mDeletedIdList = new ArrayList<Long>();
ArrayList<Long> mUpdatedIdList = new ArrayList<Long>();
public EasEmailSyncAdapter(Mailbox mailbox) {
super(mailbox);
}
@Override
public boolean parse(ByteArrayInputStream is, EasSyncService service) throws IOException {
EasEmailSyncParser p = new EasEmailSyncParser(is, service);
return p.parse();
}
class EasEmailSyncParser extends EasContentParser {
private static final String WHERE_SERVER_ID_AND_MAILBOX_KEY =
SyncColumns.SERVER_ID + "=? and " + MessageColumns.MAILBOX_KEY + "=?";
private String mMailboxIdAsString;
String[] bindArguments = new String[2];
public EasEmailSyncParser(InputStream in, EasSyncService service) throws IOException {
super(in, service);
mMailboxIdAsString = Long.toString(mMailbox.mId);
//setDebug(true); // DON'T CHECK IN WITH THIS
}
public void wipe() {
mContentResolver.delete(Message.CONTENT_URI,
Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
mContentResolver.delete(Message.DELETED_CONTENT_URI,
Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
mContentResolver.delete(Message.UPDATED_CONTENT_URI,
Message.MAILBOX_KEY + "=" + mMailbox.mId, null);
}
public void addData (Message msg) throws IOException {
String to = "";
String from = "";
String cc = "";
String replyTo = "";
int size = 0;
ArrayList<Attachment> atts = new ArrayList<Attachment>();
while (nextTag(EasTags.SYNC_APPLICATION_DATA) != END) {
switch (tag) {
case EasTags.EMAIL_ATTACHMENTS:
break;
case EasTags.EMAIL_ATTACHMENT:
attachmentParser(atts, msg);
break;
case EasTags.EMAIL_TO:
to = getValue();
break;
case EasTags.EMAIL_FROM:
from = getValue();
String sender = from;
int q = from.indexOf('\"');
if (q >= 0) {
int qq = from.indexOf('\"', q + 1);
if (qq > 0) {
sender = from.substring(q + 1, qq);
}
}
msg.mDisplayName = sender;
break;
case EasTags.EMAIL_CC:
cc = getValue();
break;
case EasTags.EMAIL_REPLY_TO:
replyTo = getValue();
break;
case EasTags.EMAIL_DATE_RECEIVED:
String date = getValue();
// 2009-02-11T18:03:03.627Z
GregorianCalendar cal = new GregorianCalendar();
cal.set(Integer.parseInt(date.substring(0, 4)), Integer.parseInt(date
.substring(5, 7)) - 1, Integer.parseInt(date.substring(8, 10)),
Integer.parseInt(date.substring(11, 13)), Integer.parseInt(date
.substring(14, 16)), Integer.parseInt(date
.substring(17, 19)));
cal.setTimeZone(TimeZone.getTimeZone("GMT"));
msg.mTimeStamp = cal.getTimeInMillis();
break;
case EasTags.EMAIL_SUBJECT:
msg.mSubject = getValue();
break;
case EasTags.EMAIL_READ:
msg.mFlagRead = getValueInt() == 1;
break;
case EasTags.EMAIL_BODY:
msg.mTextInfo = "X;X;8;" + size; // location;encoding;charset;size
msg.mText = getValue();
// For now...
msg.mPreview = "Fake preview"; // Messages.previewFromText(body);
break;
default:
skipTag();
}
}
msg.mTo = to;
msg.mFrom = from;
msg.mCc = cc;
msg.mReplyTo = replyTo;
if (atts.size() > 0) {
msg.mAttachments = atts;
}
}
public void addParser(ArrayList<Message> emails) throws IOException {
Message msg = new Message();
msg.mAccountKey = mAccount.mId;
msg.mMailboxKey = mMailbox.mId;
msg.mFlagLoaded = Message.LOADED;
while (nextTag(EasTags.SYNC_ADD) != END) {
switch (tag) {
case EasTags.SYNC_SERVER_ID:
msg.mServerId = getValue();
break;
case EasTags.SYNC_APPLICATION_DATA:
addData(msg);
break;
default:
skipTag();
}
}
// Tell the provider that this is synced back
msg.mServerVersion = mMailbox.mSyncKey;
emails.add(msg);
}
public void attachmentParser(ArrayList<Attachment> atts, Message msg) throws IOException {
String fileName = null;
String length = null;
String lvl = null;
while (nextTag(EasTags.EMAIL_ATTACHMENT) != END) {
switch (tag) {
case EasTags.EMAIL_DISPLAY_NAME:
fileName = getValue();
break;
case EasTags.EMAIL_ATT_NAME:
lvl = getValue();
break;
case EasTags.EMAIL_ATT_SIZE:
length = getValue();
break;
default:
skipTag();
}
}
if (fileName != null && length != null && lvl != null) {
Attachment att = new Attachment();
att.mEncoding = "base64";
att.mSize = Long.parseLong(length);
att.mFileName = fileName;
atts.add(att);
msg.mFlagAttachment = true;
}
}
private Cursor getServerIdCursor(String serverId, String[] projection) {
bindArguments[0] = serverId;
bindArguments[1] = mMailboxIdAsString;
return mContentResolver.query(Message.CONTENT_URI, projection,
WHERE_SERVER_ID_AND_MAILBOX_KEY, bindArguments, null);
}
public void deleteParser(ArrayList<Long> deletes) throws IOException {
while (nextTag(EasTags.SYNC_DELETE) != END) {
switch (tag) {
case EasTags.SYNC_SERVER_ID:
String serverId = getValue();
// Find the message in this mailbox with the given serverId
Cursor c = getServerIdCursor(serverId, Message.ID_COLUMN_PROJECTION);
try {
if (c.moveToFirst()) {
mService.userLog("Deleting " + serverId);
deletes.add(c.getLong(Message.ID_COLUMNS_ID_COLUMN));
}
} finally {
c.close();
}
break;
default:
skipTag();
}
}
}
class ServerChange {
long id;
boolean read;
ServerChange(long _id, boolean _read) {
id = _id;
read = _read;
}
}
public void changeParser(ArrayList<ServerChange> changes) throws IOException {
String serverId = null;
boolean oldRead = false;
boolean read = true;
long id = 0;
while (nextTag(EasTags.SYNC_CHANGE) != END) {
switch (tag) {
case EasTags.SYNC_SERVER_ID:
serverId = getValue();
Cursor c = getServerIdCursor(serverId, Message.LIST_PROJECTION);
try {
if (c.moveToFirst()) {
mService.userLog("Changing " + serverId);
oldRead = c.getInt(Message.LIST_READ_COLUMN) == Message.READ;
id = c.getLong(Message.LIST_ID_COLUMN);
}
} finally {
c.close();
}
break;
case EasTags.EMAIL_READ:
read = getValueInt() == 1;
break;
case EasTags.SYNC_APPLICATION_DATA:
break;
default:
skipTag();
}
}
if (oldRead != read) {
changes.add(new ServerChange(id, read));
}
}
public void commandsParser() throws IOException {
ArrayList<Message> newEmails = new ArrayList<Message>();
ArrayList<Long> deletedEmails = new ArrayList<Long>();
ArrayList<ServerChange> changedEmails = new ArrayList<ServerChange>();
while (nextTag(EasTags.SYNC_COMMANDS) != END) {
if (tag == EasTags.SYNC_ADD) {
addParser(newEmails);
} else if (tag == EasTags.SYNC_DELETE) {
deleteParser(deletedEmails);
} else if (tag == EasTags.SYNC_CHANGE) {
changeParser(changedEmails);
} else
skipTag();
}
// Use a batch operation to handle the changes
// TODO New mail notifications? Who looks for these?
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
for (Message content : newEmails) {
content.addSaveOps(ops);
}
for (Long id : deletedEmails) {
ops.add(ContentProviderOperation.newDelete(
ContentUris.withAppendedId(Message.CONTENT_URI, id)).build());
}
if (!changedEmails.isEmpty()) {
// Server wins in a conflict...
for (ServerChange change : changedEmails) {
// For now, don't handle read->unread
ContentValues cv = new ContentValues();
cv.put(MessageColumns.FLAG_READ, change.read);
ops.add(ContentProviderOperation.newUpdate(
ContentUris.withAppendedId(Message.CONTENT_URI, change.id))
.withValues(cv)
.build());
}
}
ops.add(ContentProviderOperation.newUpdate(
ContentUris.withAppendedId(Mailbox.CONTENT_URI, mMailbox.mId)).withValues(
mMailbox.toContentValues()).build());
// If we've sent local deletions, clear out the deleted table
for (Long id: mDeletedIdList) {
ops.add(ContentProviderOperation.newDelete(
ContentUris.withAppendedId(Message.DELETED_CONTENT_URI, id)).build());
}
try {
mService.mContext.getContentResolver()
.applyBatch(EmailProvider.EMAIL_AUTHORITY, ops);
} catch (RemoteException e) {
// There is nothing to be done here; fail by returning null
} catch (OperationApplicationException e) {
// There is nothing to be done here; fail by returning null
}
mService.userLog("SyncKey confirmed as: " + mMailbox.mSyncKey);
}
}
@Override
public String getCollectionName() {
return "Email";
}
@Override
public boolean sendLocalChanges(EasSerializer s, EasSyncService service) throws IOException {
ContentResolver cr = service.mContext.getContentResolver();
// Find any of our deleted items
Cursor c = cr.query(Message.DELETED_CONTENT_URI, Message.LIST_PROJECTION,
MessageColumns.MAILBOX_KEY + '=' + mMailbox.mId, null, null);
boolean first = true;
// We keep track of the list of deleted item id's so that we can remove them from the
// deleted table after the server receives our command
mDeletedIdList.clear();
try {
while (c.moveToNext()) {
if (first) {
s.start("Commands");
first = false;
}
// Send the command to delete this message
s.start("Delete")
.data("ServerId", c.getString(Message.LIST_SERVER_ID_COLUMN))
.end("Delete");
mDeletedIdList.add(c.getLong(Message.LIST_ID_COLUMN));
}
} finally {
c.close();
}
// Do the same now for updated items
c = cr.query(Message.UPDATED_CONTENT_URI, Message.LIST_PROJECTION,
MessageColumns.MAILBOX_KEY + '=' + mMailbox.mId, null, null);
// We keep track of the list of updated item id's so that we can remove them from the
// deleted table after the server receives our command
mUpdatedIdList.clear();
try {
while (c.moveToNext()) {
long id = c.getLong(Message.LIST_ID_COLUMN);
// Say we've handled this update
mUpdatedIdList.add(id);
// We have the id of the changed item. But first, we have to find out its current
// state, since the updated table saves the opriginal state
Cursor currentCursor = cr.query(ContentUris.withAppendedId(Message.CONTENT_URI, id),
UPDATES_PROJECTION, null, null, null);
try {
// If this item no longer exists (shouldn't be possible), just move along
if (!currentCursor.moveToFirst()) {
continue;
}
int read = currentCursor.getInt(0);
if (read == c.getInt(Message.LIST_READ_COLUMN)) {
// The read state hasn't really changed, so move on...
continue;
}
if (first) {
s.start("Commands");
first = false;
}
// Send the change to "read". We'll do "flagged" here eventually as well
// TODO Add support for flags here (EAS 12.0 and above)
s.start("Change")
.data("ServerId", c.getString(Message.LIST_SERVER_ID_COLUMN))
.start("ApplicationData")
.data("Read", Integer.toString(read))
.end("ApplicationData")
.end("Change");
} finally {
currentCursor.close();
}
}
} finally {
c.close();
}
if (!first) {
s.end("Commands");
}
return false;
}
}

View File

@ -0,0 +1,333 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to 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.adapter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.android.email.provider.EmailProvider;
import com.android.exchange.Eas;
import com.android.exchange.EasSyncService;
import com.android.exchange.MockParserStream;
import com.android.exchange.SyncManager;
import com.android.exchange.EmailContent.Account;
import com.android.exchange.EmailContent.Mailbox;
import com.android.exchange.EmailContent.MailboxColumns;
import android.content.ContentProviderOperation;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.Context;
import android.content.OperationApplicationException;
import android.database.Cursor;
import android.os.RemoteException;
import android.util.Log;
/**
* Parse the result of a FolderSync command
*
* Handles the addition, deletion, and changes to folders in the user's Exchange account.
**/
public class EasFolderSyncParser extends EasParser {
public static final String TAG = "FolderSyncParser";
// These are defined by the EAS protocol
public static final int USER_FOLDER_TYPE = 1;
public static final int INBOX_TYPE = 2;
public static final int DRAFTS_TYPE = 3;
public static final int DELETED_TYPE = 4;
public static final int SENT_TYPE = 5;
public static final int OUTBOX_TYPE = 6;
public static final int TASKS_TYPE = 7;
public static final int CALENDAR_TYPE = 8;
public static final int CONTACTS_TYPE = 9;
public static final int NOTES_TYPE = 10;
public static final int JOURNAL_TYPE = 11;
public static final int USER_MAILBOX_TYPE = 12;
public static final List<Integer> mValidFolderTypes = Arrays.asList(INBOX_TYPE, DRAFTS_TYPE,
DELETED_TYPE, SENT_TYPE, OUTBOX_TYPE, USER_MAILBOX_TYPE, CALENDAR_TYPE, CONTACTS_TYPE);
private static final String WHERE_SERVER_ID_AND_ACCOUNT = MailboxColumns.SERVER_ID + "=? and " +
MailboxColumns.ACCOUNT_KEY + "=?";
private static final String WHERE_DISPLAY_NAME_AND_ACCOUNT = MailboxColumns.DISPLAY_NAME +
"=? and " + MailboxColumns.ACCOUNT_KEY + "=?";
private static final String WHERE_PARENT_SERVER_ID_AND_ACCOUNT =
MailboxColumns.PARENT_SERVER_ID +"=? and " + MailboxColumns.ACCOUNT_KEY + "=?";
private static final String[] MAILBOX_ID_COLUMNS_PROJECTION =
new String[] {MailboxColumns.ID, MailboxColumns.SERVER_ID};
private Account mAccount;
private long mAccountId;
private String mAccountIdAsString;
private EasSyncService mService;
private Context mContext;
private ContentResolver mContentResolver;
private MockParserStream mMock = null;
private String[] mBindArguments = new String[2];
public EasFolderSyncParser(InputStream in, EasSyncService service) throws IOException {
super(in);
mService = service;
mAccount = service.mAccount;
mAccountId = mAccount.mId;
mAccountIdAsString = Long.toString(mAccountId);
mContext = service.mContext;
mContentResolver = mContext.getContentResolver();
if (in instanceof MockParserStream) {
mMock = (MockParserStream)in;
}
setDebug(true);
}
public boolean parse() throws IOException {
int status;
boolean res = false;
if (nextTag(START_DOCUMENT) != EasTags.FOLDER_FOLDER_SYNC)
throw new IOException();
while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
if (tag == EasTags.FOLDER_STATUS) {
status = getValueInt();
if (status != Eas.FOLDER_STATUS_OK) {
mService.errorLog("FolderSync failed: " + status);
if (status == Eas.FOLDER_STATUS_INVALID_KEY) {
mAccount.mSyncKey = "0";
mService.errorLog("Bad sync key; RESET and delete all folders");
mContentResolver.delete(Mailbox.CONTENT_URI,
MailboxColumns.ACCOUNT_KEY + '=' + mAccountId, null);
// Stop existing syncs and reconstruct _main
SyncManager.folderListReloaded(mAccountId);
res = true;
} else {
// Other errors are at the server, so let's throw an error that will
// cause this sync to be retried at a later time
mService.errorLog("Throwing IOException; will retry later");
throw new IOException();
}
}
} else if (tag == EasTags.FOLDER_SYNC_KEY) {
mAccount.mSyncKey = getValue();
mService.userLog("New Account SyncKey: " + mAccount.mSyncKey);
} else if (tag == EasTags.FOLDER_CHANGES) {
changesParser();
} else
skipTag();
}
mAccount.saveOrUpdate(mContext);
return res;
}
private Cursor getServerIdCursor(String serverId) {
mBindArguments[0] = serverId;
mBindArguments[1] = mAccountIdAsString;
return mContentResolver.query(Mailbox.CONTENT_URI, new String[] {MailboxColumns.ID},
WHERE_SERVER_ID_AND_ACCOUNT, mBindArguments, null);
}
public void deleteParser(ArrayList<ContentProviderOperation> ops) throws IOException {
while (nextTag(EasTags.SYNC_DELETE) != END) {
switch (tag) {
case EasTags.FOLDER_SERVER_ID:
String serverId = getValue();
// Find the mailbox in this account with the given serverId
Cursor c = getServerIdCursor(serverId);
try {
if (c.moveToFirst()) {
mService.userLog("Deleting " + serverId);
ops.add(ContentProviderOperation.newDelete(
ContentUris.withAppendedId(Mailbox.CONTENT_URI,
c.getLong(0))).build());
}
} finally {
c.close();
}
break;
default:
skipTag();
}
}
}
public void addParser(ArrayList<ContentProviderOperation> ops) throws IOException {
String name = null;
String serverId = null;
String parentId = null;
int type = 0;
while (nextTag(EasTags.FOLDER_ADD) != END) {
switch (tag) {
case EasTags.FOLDER_DISPLAY_NAME: {
name = getValue();
break;
}
case EasTags.FOLDER_TYPE: {
type = getValueInt();
break;
}
case EasTags.FOLDER_PARENT_ID: {
parentId = getValue();
break;
}
case EasTags.FOLDER_SERVER_ID: {
serverId = getValue();
break;
}
default:
skipTag();
}
}
if (mValidFolderTypes.contains(type)) {
Mailbox m = new Mailbox();
m.mDisplayName = name;
m.mServerId = serverId;
m.mAccountKey = mAccountId;
m.mSyncFrequency = Account.CHECK_INTERVAL_NEVER;
switch (type) {
case INBOX_TYPE:
m.mSyncFrequency = Account.CHECK_INTERVAL_PUSH;
m.mType = Mailbox.TYPE_INBOX;
break;
case OUTBOX_TYPE:
m.mSyncFrequency = Account.CHECK_INTERVAL_NEVER;
// TYPE_OUTBOX mailboxes are known by SyncManager to sync whenever they aren't
// empty. The value of mSyncFrequency is ignored for this kind of mailbox.
m.mType = Mailbox.TYPE_OUTBOX;
break;
case SENT_TYPE:
m.mType = Mailbox.TYPE_SENT;
break;
case DRAFTS_TYPE:
m.mType = Mailbox.TYPE_DRAFTS;
break;
case DELETED_TYPE:
m.mType = Mailbox.TYPE_TRASH;
break;
case CALENDAR_TYPE:
m.mType = Mailbox.TYPE_CALENDAR;
// TODO This could be push, depending on settings
// For now, no sync, since it's not yet implemented
break;
case CONTACTS_TYPE:
m.mType = Mailbox.TYPE_CONTACTS;
// TODO Frequency below should depend on settings
m.mSyncFrequency = Account.CHECK_INTERVAL_PUSH;
break;
}
// Make boxes like Contacts and Calendar invisible in the folder list
m.mFlagVisible = (m.mType < Mailbox.TYPE_NOT_EMAIL);
if (!parentId.equals("0")) {
m.mParentServerId = parentId;
}
Log.v(TAG, "Adding mailbox: " + m.mDisplayName);
ops.add(ContentProviderOperation
.newInsert(Mailbox.CONTENT_URI).withValues(m.toContentValues()).build());
}
return;
}
public void changesParser() throws IOException {
// Keep track of new boxes, deleted boxes, updated boxes
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
while (nextTag(EasTags.FOLDER_CHANGES) != END) {
// TODO Handle FOLDER_CHANGE and FOLDER_DELETE
if (tag == EasTags.FOLDER_ADD) {
addParser(ops);
} else if (tag == EasTags.FOLDER_DELETE) {
deleteParser(ops);
} else if (tag == EasTags.FOLDER_COUNT) {
getValueInt();
} else
skipTag();
}
// The mock stream is used for junit tests, so that the parsing code can be tested
// separately from the provider code.
// TODO Change tests to not require this; remove references to the mock stream
if (mMock != null) {
mMock.setResult(null);
return;
}
// Create the new mailboxes in a single batch operation
if (!ops.isEmpty()) {
mService.userLog("Applying " + ops.size() + " mailbox operations.");
// Then, we create an update for the account (most importantly, updating the syncKey)
ops.add(ContentProviderOperation.newUpdate(
ContentUris.withAppendedId(Account.CONTENT_URI, mAccountId)).withValues(
mAccount.toContentValues()).build());
// Finally, we execute the batch
try {
mService.mContext.getContentResolver()
.applyBatch(EmailProvider.EMAIL_AUTHORITY, ops);
mService.userLog("New syncKey: " + mAccount.mSyncKey);
} catch (RemoteException e) {
// There is nothing to be done here; fail by returning null
} catch (OperationApplicationException e) {
// There is nothing to be done here; fail by returning null
}
// Look for sync issues and its children and delete them
// I'm not aware of any other way to deal with this properly
mBindArguments[0] = "Sync Issues";
mBindArguments[1] = mAccountIdAsString;
Cursor c = mContentResolver.query(Mailbox.CONTENT_URI, MAILBOX_ID_COLUMNS_PROJECTION,
WHERE_DISPLAY_NAME_AND_ACCOUNT, mBindArguments, null);
String parentServerId = null;
long id = 0;
try {
if (c.moveToFirst()) {
id = c.getLong(0);
parentServerId = c.getString(1);
}
} finally {
c.close();
}
if (parentServerId != null) {
mContentResolver.delete(ContentUris.withAppendedId(Mailbox.CONTENT_URI, id),
null, null);
mBindArguments[0] = parentServerId;
mContentResolver.delete(Mailbox.CONTENT_URI, WHERE_PARENT_SERVER_ID_AND_ACCOUNT,
mBindArguments);
}
}
}
}

View File

@ -15,32 +15,39 @@
* limitations under the License.
*/
package com.android.exchange;
package com.android.exchange.adapter;
import java.io.IOException;
import java.io.InputStream;
import android.util.Log;
import com.android.exchange.EasSyncService;
import com.android.exchange.EmailContent.Mailbox;
/**
* Parse the result of a Move command
*
* This is currently unused, as "move to folder" is not implemented in the application.
**/
public class EasMoveParser extends EasParser {
private static final String TAG = "EasMoveParser";
private EasService mService;
private EasSyncService mService;
private Mailbox mMailbox;
protected boolean mMoreAvailable = false;
public EasMoveParser(InputStream in, EasService service) throws IOException {
public EasMoveParser(InputStream in, EasSyncService service) throws IOException {
super(in);
mService = service;
mMailbox = service.mMailbox;
setDebug(true);
//setDebug(true);
}
public void parse() throws IOException {
public boolean parse() throws IOException {
int status;
if (nextTag(START_DOCUMENT) != EasTags.MOVE_MOVE_ITEMS)
if (nextTag(START_DOCUMENT) != EasTags.MOVE_MOVE_ITEMS) {
throw new IOException();
}
while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
if (tag == EasTags.MOVE_RESPONSE) {
// Ignore
@ -50,10 +57,13 @@ public class EasMoveParser extends EasParser {
Log.e(TAG, "Sync failed (3 is success): " + status);
}
} else if (tag == EasTags.SYNC_RESPONSES) {
// TODO See if any of these cases need to be handled
skipTag();
} else
} else {
skipTag();
}
}
mMailbox.save(mService.mContext);
return false;
}
}

View File

@ -15,20 +15,25 @@
* limitations under the License.
*/
package com.android.exchange;
package com.android.exchange.adapter;
import java.io.IOException;
import java.net.HttpURLConnection;
import com.android.exchange.EasSyncService;
import com.android.exchange.SyncManager;
import com.android.exchange.EmailContent.HostAuth;
import com.android.exchange.EmailContent.Mailbox;
import com.android.exchange.EmailContent.Message;
import com.android.exchange.EmailContent.MessageColumns;
import com.android.exchange.utility.Rfc822Formatter;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
public class EasOutboxService extends EasService {
public class EasOutboxService extends EasSyncService {
public EasOutboxService(Context _context, Mailbox _mailbox) {
super(_context, _mailbox);
@ -39,26 +44,24 @@ public class EasOutboxService extends EasService {
mPassword = ha.mPassword;
}
public void run () {
public void run() {
mThread = Thread.currentThread();
String uniqueId = android.provider.Settings.System.getString(mContext.getContentResolver(),
android.provider.Settings.System.ANDROID_ID);
try {
Cursor c = mContext.getContentResolver().query(Message.CONTENT_URI,
Message.CONTENT_PROJECTION, "mMailbox=" + mMailbox, null, null);
Message.CONTENT_PROJECTION, MessageColumns.MAILBOX_KEY + '=' + mMailbox,
null, null);
try {
if (c.moveToFirst()) {
while (c.moveToNext()) {
Message msg = new Message().restore(c);
if (msg != null) {
String data = Rfc822Formatter
.writeEmailAsRfc822String(mContext, mAccount, msg, uniqueId);
.writeEmailAsRfc822String(mContext, mAccount, msg, uniqueId);
HttpURLConnection uc = sendEASPostCommand("SendMail&SaveInSent=T", data);
int code = uc.getResponseCode();
//Intent intent = new Intent(MessageListView.MAIL_UPDATE);
//intent.putExtra("type", "toast");
if (code == HttpURLConnection.HTTP_OK) {
//intent.putExtra("text", "Your message with subject \"" + msg.mSubject + "\" has been sent.");
log("Deleting message...");
userLog("Deleting message...");
mContext.getContentResolver().delete(ContentUris.withAppendedId(
Message.CONTENT_URI, msg.mId), null, null);
} else {
@ -66,19 +69,21 @@ public class EasOutboxService extends EasService {
cv.put("uid", 1);
Message.update(mContext,
Message.CONTENT_URI, msg.mId, cv);
//intent.putExtra("text", "WHOA! Your message with subject \"" + msg.mSubject + "\" failed to send.");
}
//mContext.sendBroadcast(intent);
updateUI();
// TODO How will the user know that the message sent or not?
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
c.close();
}
} catch (RuntimeException e1) {
e1.printStackTrace();
} catch (IOException e) {
userLog("Caught IOException");
mExitStatus = EXIT_IO_ERROR;
} catch (Exception e) {
mExitStatus = EXIT_EXCEPTION;
} finally {
userLog(mMailbox.mDisplayName + ": sync finished");
SyncManager.done(this);
}
}
}

View File

@ -0,0 +1,470 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to 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.adapter;
import java.io.*;
import java.util.ArrayList;
import com.android.exchange.EasException;
import android.content.Context;
import android.util.Log;
/**
* Extremely fast and lightweight WBXML parser, implementing only the subset of WBXML that
* EAS uses (as defined in the EAS specification)
*
*/
public abstract class EasParser {
private static final String TAG = "EasParser";
// The following constants are Wbxml standard
public static final int START_DOCUMENT = 0;
public static final int DONE = 1;
public static final int START = 2;
public static final int END = 3;
public static final int TEXT = 4;
public static final int END_DOCUMENT = 3;
private static final int NOT_FETCHED = Integer.MIN_VALUE;
private static final int NOT_ENDED = Integer.MIN_VALUE;
private static final int EOF_BYTE = -1;
private boolean debug = false;
private boolean capture = false;
private ArrayList<Integer> captureArray;
// The input stream for this parser
private InputStream in;
// The current tag depth
private int depth;
// The upcoming (saved) id from the stream
private int nextId = NOT_FETCHED;
// The current tag table (i.e. the tag table for the current page)
private String[] tagTable;
// An array of tag tables, as defined in EasTags
static private String[][] tagTables = new String[24][];
// The stack of names of tags being processed; used when debug = true
private String[] nameArray = new String[32];
// The stack of tags being processed
private int[] startTagArray = new int[32];
// The following vars are available to all to avoid method calls that represent the state of
// the parser at any given time
public int endTag = NOT_ENDED;
public int startTag;
// The type of the last token read
public int type;
// The current page
public int page;
// The current tag
public int tag;
// The name of the current tag
public String name;
// Whether the current tag is associated with content (a value)
private boolean noContent;
// The value read, as a String. Only one of text or num will be valid, depending on whether the
// value was requested as a String or an int (to avoid wasted effort in parsing)
public String text;
// The value read, as an int
public int num;
public class EofException extends IOException {
private static final long serialVersionUID = 1L;
}
public class EodException extends IOException {
private static final long serialVersionUID = 1L;
}
public class EasParserException extends IOException {
private static final long serialVersionUID = 1L;
}
public boolean parse() throws IOException, EasException {
return false;
}
/**
* Initialize the tag tables; they are constant
*
*/
{
String[][] pages = EasTags.pages;
for (int i = 0; i < pages.length; i++) {
String[] page = pages[i];
if (page.length > 0) {
tagTables[i] = page;
}
}
}
public EasParser(InputStream in) throws IOException {
setInput(in);
}
/**
* Set the debug state of the parser. When debugging is on, every token is logged (Log.v) to
* the console.
*
* @param val the desired state for debug output
*/
public void setDebug(boolean val) {
debug = val;
}
/**
* Turns on data capture; this is used to create test streams that represent "live" data and
* can be used against the various parsers.
*/
public void captureOn() {
capture = true;
captureArray = new ArrayList<Integer>();
}
/**
* Turns off data capture; writes the captured data to a specified file.
*/
public void captureOff(Context context, String file) {
try {
FileOutputStream out = context.openFileOutput(file, Context.MODE_WORLD_WRITEABLE);
out.write(captureArray.toString().getBytes());
out.close();
} catch (FileNotFoundException e) {
// This is debug code; exceptions aren't interesting.
} catch (IOException e) {
// This is debug code; exceptions aren't interesting.
}
}
/**
* Return the value of the current tag, as a String
*
* @return the String value of the current tag
* @throws IOException
*/
public String getValue() throws IOException {
// The false argument tells getNext to return the value as a String
getNext(false);
// Save the value
String val = text;
// Read the next token; it had better be the end of the current tag
getNext(false);
// If not, throw an exception
if (type != END) {
throw new IOException("No END found!");
}
endTag = startTag;
return val;
}
/**
* Return the value of the current tag, as an integer
*
* @return the integer value of the current tag
* @throws IOException
*/
public int getValueInt() throws IOException {
// The true argument to getNext indicates the desire for an integer return value
getNext(true);
// Save the value
int val = num;
// Read the next token; it had better be the end of the current tag
getNext(false);
// If not, throw an exception
if (type != END) {
throw new IOException("No END found!");
}
endTag = startTag;
return val;
}
/**
* Return the next tag found in the stream; special tags END and END_DOCUMENT are used to
* mark the end of the current tag and end of document. If we hit end of document without
* looking for it, generate an EodException. The tag returned consists of the page number
* shifted PAGE_SHIFT bits OR'd with the tag retrieved from the stream. Thus, all tags returned
* are unique.
*
* @param endingTag the tag that would represent the end of the tag we're processing
* @return the next tag found
* @throws IOException
*/
public int nextTag(int endingTag) throws IOException {
// Lose the page information
endTag = endingTag &= EasTags.PAGE_MASK;
while (getNext(false) != DONE) {
// If we're a start, set tag to include the page and return it
if (type == START) {
tag = page | startTag;
return tag;
// If we're at the ending tag we're looking for, return the END signal
} else if (type == END && startTag == endTag) {
return END;
}
}
// We're at end of document here. If we're looking for it, return END_DOCUMENT
if (endTag == START_DOCUMENT) {
return END_DOCUMENT;
}
// Otherwise, we've prematurely hit end of document, so exception out
// EodException is a subclass of IOException; this will be treated as an IO error by
// SyncManager.
throw new EodException();
}
/**
* Skip anything found in the stream until the end of the current tag is reached. This can be
* used to ignore stretches of xml that aren't needed by the parser.
*
* @throws IOException
*/
public void skipTag() throws IOException {
int thisTag = startTag;
// Just loop until we hit the end of the current tag
while (getNext(false) != DONE) {
if (type == END && startTag == thisTag) {
return;
}
}
// If we're at end of document, that's bad
throw new EofException();
}
/**
* Retrieve the next token from the input stream
*
* @return the token found
* @throws IOException
*/
public int nextToken() throws IOException {
getNext(false);
return type;
}
/**
* Initializes the parser with an input stream; reads the first 4 bytes (which are always the
* same in EAS, and then sets the tag table to point to page 0 (by definition, the starting
* page).
*
* @param in the InputStream associated with this parser
* @throws IOException
*/
public void setInput(InputStream in) throws IOException {
this.in = in;
readByte(); // version
readInt(); // ?
readInt(); // 106 (UTF-8)
readInt(); // string table length
tagTable = tagTables[0];
}
/**
* Return the next piece of data from the stream. The return value indicates the type of data
* that has been retrieved - START (start of tag), END (end of tag), DONE (end of stream), or
* TEXT (the value of a tag)
*
* @param asInt whether a TEXT value should be parsed as a String or an int.
* @return the type of data retrieved
* @throws IOException
*/
private final int getNext(boolean asInt) throws IOException {
if (type == END) {
depth--;
} else {
endTag = NOT_ENDED;
}
if (noContent) {
type = END;
noContent = false;
return type;
}
text = null;
name = null;
int id = nextId ();
while (id == Wbxml.SWITCH_PAGE) {
nextId = NOT_FETCHED;
// Get the new page number
int pg = readByte();
// Save the shifted page to add into the startTag in nextTag
page = pg << EasTags.PAGE_SHIFT;
// Retrieve the current tag table
tagTable = tagTables[pg];
id = nextId();
}
nextId = NOT_FETCHED;
switch (id) {
case EOF_BYTE:
// End of document
type = DONE;
break;
case Wbxml.END:
// End of tag
type = END;
if (debug) {
name = nameArray[depth];
Log.v(TAG, "</" + name + '>');
}
// Retrieve the now-current startTag from our stack
startTag = endTag = startTagArray[depth];
break;
case Wbxml.STR_I:
// Inline string
type = TEXT;
if (asInt) {
num = readInlineInt();
} else {
text = readInlineString();
}
if (debug) {
Log.v(TAG, asInt ? Integer.toString(num) : text);
}
break;
default:
// Start of tag
type = START;
// The tag is in the low 6 bits
startTag = id & 0x3F;
// If the high bit is set, there is content (a value) to be read
noContent = (id & 0x40) == 0;
depth++;
if (debug) {
name = tagTable[startTag - 5];
Log.v(TAG, '<' + name + '>');
nameArray[depth] = name;
}
// Save the startTag to our stack
startTagArray[depth] = startTag;
}
// Return the type of data we're dealing with
return type;
}
/**
* Read an int from the input stream, and capture it if necessary for debugging. Seems a small
* price to pay...
*
* @return the int read
* @throws IOException
*/
private int read() throws IOException {
int i;
i = in.read();
if (capture) {
captureArray.add(i);
}
return i;
}
private int nextId() throws IOException {
if (nextId == NOT_FETCHED) {
nextId = read();
}
return nextId;
}
private int readByte() throws IOException {
int i = read();
if (i == EOF_BYTE) {
throw new EofException();
}
return i;
}
/**
* Read an integer from the stream; this is called when the parser knows that what follows is
* an inline string representing an integer (e.g. the Read tag in Email has a value known to
* be either "0" or "1")
*
* @return the integer as parsed from the stream
* @throws IOException
*/
private int readInlineInt() throws IOException {
int result = 0;
while (true) {
int i = readByte();
// Inline strings are always terminated with a zero byte
if (i == 0) {
return result;
}
if (i >= '0' && i <= '9') {
result = (result * 10) + (i - '0');
} else {
throw new IOException("Non integer");
}
}
}
private int readInt() throws IOException {
int result = 0;
int i;
do {
i = readByte();
result = (result << 7) | (i & 0x7f);
} while ((i & 0x80) != 0);
return result;
}
/**
* Read an inline string from the stream
*
* @return the String as parsed from the stream
* @throws IOException
*/
private String readInlineString() throws IOException {
StringBuilder sb = new StringBuilder(256);
while (true) {
int i = read();
if (i == 0) {
break;
} else if (i == EOF_BYTE) {
throw new EofException();
}
sb.append((char)i);
}
String res = sb.toString();
return res;
}
}

View File

@ -0,0 +1,85 @@
/* Copyright (C) 2008-2009 Marc Blank
* Licensed to 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.adapter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import com.android.exchange.EasSyncService;
import com.android.exchange.StaleFolderListException;
/**
* Parse the result of a Ping command.
*
* If there are folders with changes, add the serverId of those folders to the syncList array.
* If the folder list needs to be reloaded, throw a StaleFolderListException, which will be caught
* by the sync server, which will sync the updated folder list.
*/
public class EasPingParser extends EasParser {
ArrayList<String> syncList = new ArrayList<String>();
EasSyncService mService;
public ArrayList<String> getSyncList() {
return syncList;
}
public EasPingParser(InputStream in, EasSyncService _service) throws IOException {
super(in);
mService = _service;
//setDebug(true);
}
public void parsePingFolders(ArrayList<String> syncList) throws IOException {
while (nextTag(EasTags.PING_FOLDERS) != END) {
if (tag == EasTags.PING_FOLDER) {
// Here we'll keep track of which mailboxes need syncing
syncList.add(getValue());
} else {
skipTag();
}
}
}
@Override
public boolean parse() throws IOException, StaleFolderListException {
boolean res = false;
if (nextTag(START_DOCUMENT) != EasTags.PING_PING) {
throw new IOException();
}
while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
if (tag == EasTags.PING_STATUS) {
int status = getValueInt();
mService.userLog("Ping completed, status = " + status);
if (status == 2) {
// Status = 2 indicates changes in one folder or other
res = true;
} else if (status == 7 || status == 4) {
// Status of 7 or 4 indicate a stale folder list
throw new StaleFolderListException();
}
} else if (tag == EasTags.PING_FOLDERS) {
parsePingFolders(syncList);
} else {
skipTag();
}
}
return res;
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to 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.adapter;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Hashtable;
/**
* This is a convenience class that simplifies the creation of Wbxml commands and allows
* multiple commands to be chained together.
*
* Each start command must pair with an end command; the values of all data fields are Strings. The
* methods here should be self-explanatory.
*
* Use toString() to obtain the output for the EAS server
*/
public class EasSerializer extends WbxmlSerializer {
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
static Hashtable<String, Object> tagTable = null;
public EasSerializer() {
super();
try {
setOutput(byteStream, null);
// Lazy initialization of our tag tables, as created from EasTags
if (tagTable == null) {
String[][] pages = EasTags.pages;
for (int i = 0; i < pages.length; i++) {
String[] page = pages[i];
if (page.length > 0) {
setTagTable(i, page);
}
}
tagTable = getTagTable();
} else {
setTagTable(tagTable);
}
startDocument("UTF-8", false);
} catch (IOException e) {
e.printStackTrace();
}
}
public EasSerializer start(String tag) throws IOException {
startTag(null, tag);
return this;
}
public EasSerializer end(String tag) throws IOException {
endTag(null, tag);
return this;
}
public EasSerializer end() throws IOException {
endDocument();
return this;
}
public EasSerializer data(String tag, String value) throws IOException {
startTag(null, tag);
text(value);
endTag(null, tag);
return this;
}
public EasSerializer tag(String tag) throws IOException {
startTag(null, tag);
endTag(null, tag);
return this;
}
public EasSerializer text(String str) throws IOException {
super.text(str);
return this;
}
public ByteArrayOutputStream getByteStream() {
return byteStream;
}
public String toString() {
return byteStream.toString();
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to 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.adapter;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import com.android.exchange.EasSyncService;
import com.android.exchange.EmailContent.Mailbox;
/**
* Parent class of all sync adapters (EasMailbox, EasCalendar, and EasContacts)
*
*/
public abstract class EasSyncAdapter {
public Mailbox mMailbox;
// Create the data for local changes that need to be sent up to the server
public abstract boolean sendLocalChanges(EasSerializer s, EasSyncService service)
throws IOException;
// Parse incoming data from the EAS server, creating, modifying, and deleting objects as
// required through the EmailProvider
public abstract boolean parse(ByteArrayInputStream is, EasSyncService service)
throws IOException;
// The name used to specify the collection type of the target (Email, Calendar, or Contacts)
public abstract String getCollectionName();
public EasSyncAdapter(Mailbox mailbox) {
mMailbox = mailbox;
}
}

View File

@ -0,0 +1,459 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to 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.adapter;
/**
* The wbxml tags for EAS are all defined here.
*
* The static final int's, of the form <page>_<tag> = <constant> are used in parsing incoming
* responses from the server (i.e. EasParser and its subclasses).
*
* The array of String arrays is used to construct server requests with EasSerializer. One thing
* we might do eventually is to "precompile" these requests, in part, although they should be
* fairly fast to begin with (each tag requires one HashMap lookup, and there aren't all that many
* of them in a given command).
*
*/
public class EasTags {
// Wbxml page definitions for EAS
static final int AIRSYNC = 0x00;
static final int CONTACTS = 0x01;
static final int EMAIL = 0x02;
static final int CALENDAR = 0x04;
static final int MOVE = 0x05;
static final int FOLDER = 0x07;
static final int CONTACTS2 = 0x0C;
static final int PING = 0x0D;
static final int GAL = 0x10;
static final int BASE = 0x11;
// Shift applied to page numbers to generate tag
static final int PAGE_SHIFT = 6;
static final int PAGE_MASK = 0x3F; // 6 bits
static final int SYNC_PAGE = 0 << PAGE_SHIFT;
static final int SYNC_SYNC = SYNC_PAGE + 5;
static final int SYNC_RESPONSES = SYNC_PAGE + 6;
static final int SYNC_ADD = SYNC_PAGE + 7;
static final int SYNC_CHANGE = SYNC_PAGE + 8;
static final int SYNC_DELETE = SYNC_PAGE + 9;
static final int SYNC_FETCH = SYNC_PAGE + 0xA;
static final int SYNC_SYNC_KEY = SYNC_PAGE + 0xB;
static final int SYNC_CLIENT_ID = SYNC_PAGE + 0xC;
static final int SYNC_SERVER_ID = SYNC_PAGE + 0xD;
static final int SYNC_STATUS = SYNC_PAGE + 0xE;
static final int SYNC_COLLECTION = SYNC_PAGE + 0xF;
static final int SYNC_CLASS = SYNC_PAGE + 0x10;
static final int SYNC_VERSION = SYNC_PAGE + 0x11;
static final int SYNC_COLLECTION_ID = SYNC_PAGE + 0x12;
static final int SYNC_GET_CHANGES = SYNC_PAGE + 0x13;
static final int SYNC_MORE_AVAILABLE = SYNC_PAGE + 0x14;
static final int SYNC_WINDOW_SIZE = SYNC_PAGE + 0x15;
static final int SYNC_COMMANDS = SYNC_PAGE + 0x16;
static final int SYNC_OPTIONS = SYNC_PAGE + 0x17;
static final int SYNC_FILTER_TYPE = SYNC_PAGE + 0x18;
static final int SYNC_TRUNCATION = SYNC_PAGE + 0x19;
static final int SYNC_RTF_TRUNCATION = SYNC_PAGE + 0x1A;
static final int SYNC_CONFLICT = SYNC_PAGE + 0x1B;
static final int SYNC_COLLECTIONS = SYNC_PAGE + 0x1C;
static final int SYNC_APPLICATION_DATA = SYNC_PAGE + 0x1D;
static final int SYNC_DELETES_AS_MOVES = SYNC_PAGE + 0x1E;
static final int SYNC_NOTIFY_GUID = SYNC_PAGE + 0x1F;
static final int SYNC_SUPPORTED = SYNC_PAGE + 0x20;
static final int SYNC_SOFT_DELETE = SYNC_PAGE + 0x21;
static final int SYNC_MIME_SUPPORT = SYNC_PAGE + 0x22;
static final int SYNC_MIME_TRUNCATION = SYNC_PAGE + 0x23;
static final int SYNC_WAIT = SYNC_PAGE + 0x24;
static final int SYNC_LIMIT = SYNC_PAGE + 0x25;
static final int SYNC_PARTIAL = SYNC_PAGE + 0x26;
static final int CONTACTS_PAGE = CONTACTS << PAGE_SHIFT;
static final int CONTACTS_ANNIVERSARY = CONTACTS_PAGE + 5;
static final int CONTACTS_ASSISTANT_NAME = CONTACTS_PAGE + 6;
static final int CONTACTS_ASSISTANT_TELEPHONE_NUMBER = CONTACTS_PAGE + 7;
static final int CONTACTS_BIRTHDAY = CONTACTS_PAGE + 8;
static final int CONTACTS_BODY = CONTACTS_PAGE + 9;
static final int CONTACTS_BODY_SIZE = CONTACTS_PAGE + 0xA;
static final int CONTACTS_BODY_TRUNCATED = CONTACTS_PAGE + 0xB;
static final int CONTACTS_BUSINESS2_TELEPHONE_NUMBER = CONTACTS_PAGE + 0xC;
static final int CONTACTS_BUSINESS_ADDRESS_CITY = CONTACTS_PAGE + 0xD;
static final int CONTACTS_BUSINESS_ADDRESS_COUNTRY = CONTACTS_PAGE + 0xE;
static final int CONTACTS_BUSINESS_ADDRESS_POSTAL_CODE = CONTACTS_PAGE + 0xF;
static final int CONTACTS_BUSINESS_ADDRESS_STATE = CONTACTS_PAGE + 0x10;
static final int CONTACTS_BUSINESS_ADDRESS_STREET = CONTACTS_PAGE + 0x11;
static final int CONTACTS_BUSINESS_FAX_NUMBER = CONTACTS_PAGE + 0x12;
static final int CONTACTS_BUSINESS_TELEPHONE_NUMBER = CONTACTS_PAGE + 0x13;
static final int CONTACTS_CAR_TELEPHONE_NUMBER = CONTACTS_PAGE + 0x14;
static final int CONTACTS_CATEGORIES = CONTACTS_PAGE + 0x15;
static final int CONTACTS_CATEGORY = CONTACTS_PAGE + 0x16;
static final int CONTACTS_CHILDREN = CONTACTS_PAGE + 0x17;
static final int CONTACTS_CHILD = CONTACTS_PAGE + 0x18;
static final int CONTACTS_COMPANY_NAME = CONTACTS_PAGE + 0x19;
static final int CONTACTS_DEPARTMENT = 0x1A;
static final int CONTACTS_EMAIL1_ADDRESS = CONTACTS_PAGE + 0x1B;
static final int CONTACTS_EMAIL2_ADDRESS = CONTACTS_PAGE + 0x1C;
static final int CONTACTS_EMAIL3_ADDRESS = CONTACTS_PAGE + 0x1D;
static final int CONTACTS_FILE_AS = CONTACTS_PAGE + 0x1E;
static final int CONTACTS_FIRST_NAME = CONTACTS_PAGE + 0x1F;
static final int CONTACTS_HOME2_TELEPHONE_NUMBER = CONTACTS_PAGE + 0x20;
static final int CONTACTS_HOME_ADDRESS_CITY = CONTACTS_PAGE + 0x21;
static final int CONTACTS_HOME_ADDRESS_COUNTRY = CONTACTS_PAGE + 0x22;
static final int CONTACTS_HOME_ADDRESS_POSTAL_CODE = CONTACTS_PAGE + 0x23;
static final int CONTACTS_HOME_ADDRESS_STATE = CONTACTS_PAGE + 0x24;
static final int CONTACTS_HOME_ADDRESS_STREET = CONTACTS_PAGE + 0x25;
static final int CONTACTS_HOME_FAX_NUMBER = CONTACTS_PAGE + 0x26;
static final int CONTACTS_HOME_TELEPHONE_NUMBER = CONTACTS_PAGE + 0x27;
static final int CONTACTS_JOB_TITLE = CONTACTS_PAGE + 0x28;
static final int CONTACTS_LAST_NAME = CONTACTS_PAGE + 0x29;
static final int CONTACTS_MIDDLE_NAME = CONTACTS_PAGE + 0x2A;
static final int CONTACTS_MOBILE_TELEPHONE_NUMBER = CONTACTS_PAGE + 0x2B;
static final int CONTACTS_OFFICE_LOCATION = CONTACTS_PAGE + 0x2C;
static final int CONTACTS_OTHER_ADDRESS_CITY = CONTACTS_PAGE + 0x2D;
static final int CONTACTS_OTHER_ADDRESS_COUNTRY = CONTACTS_PAGE + 0x2E;
static final int CONTACTS_OTHER_ADDRESS_POSTAL_CODE = CONTACTS_PAGE + 0x2F;
static final int CONTACTS_OTHER_ADDRESS_STATE = CONTACTS_PAGE + 0x30;
static final int CONTACTS_OTHER_ADDRESS_STREET = CONTACTS_PAGE + 0x31;
static final int CONTACTS_PAGER_NUMBER = CONTACTS_PAGE + 0x32;
static final int CONTACTS_RADIO_TELEPHONE_NUMBER = CONTACTS_PAGE + 0x33;
static final int CONTACTS_SPOUSE = CONTACTS_PAGE + 0x34;
static final int CONTACTS_SUFFIX = CONTACTS_PAGE + 0x35;
static final int CONTACTS_TITLE = CONTACTS_PAGE + 0x36;
static final int CONTACTS_WEBPAGE = CONTACTS_PAGE + 0x37;
static final int CONTACTS_YOMI_COMPANY_NAME = CONTACTS_PAGE + 0x38;
static final int CONTACTS_YOMI_FIRST_NAME = CONTACTS_PAGE + 0x39;
static final int CONTACTS_YOMI_LAST_NAME = CONTACTS_PAGE + 0x3A;
static final int CONTACTS_COMPRESSED_RTF = CONTACTS_PAGE + 0x3B;
static final int CONTACTS_PICTURE = CONTACTS_PAGE + 0x3C;
static final int CALENDAR_PAGE = CALENDAR << PAGE_SHIFT;
static final int CALENDAR_TIME_ZONE = CALENDAR_PAGE + 5;
static final int CALENDAR_ALL_DAY_EVENT = CALENDAR_PAGE + 6;
static final int CALENDAR_ATTENDEES = CALENDAR_PAGE + 7;
static final int CALENDAR_ATTENDEE = CALENDAR_PAGE + 8;
static final int CALENDAR_ATTENDEE_EMAIL = CALENDAR_PAGE + 9;
static final int CALENDAR_ATTENDEE_NAME = CALENDAR_PAGE + 0xA;
static final int CALENDAR_BODY = CALENDAR_PAGE + 0xB;
static final int CALENDAR_BODY_TRUNCATED = CALENDAR_PAGE + 0xC;
static final int CALENDAR_BUSY_STATUS = CALENDAR_PAGE + 0xD;
static final int CALENDAR_CATEGORIES = CALENDAR_PAGE + 0xE;
static final int CALENDAR_CATEGORY = CALENDAR_PAGE + 0xF;
static final int CALENDAR_COMPRESSED_RTF = CALENDAR_PAGE + 0x10;
static final int CALENDAR_DTSTAMP = CALENDAR_PAGE + 0x11;
static final int CALENDAR_END_TIME = CALENDAR_PAGE + 0x12;
static final int CALENDAR_EXCEPTION = CALENDAR_PAGE + 0x13;
static final int CALENDAR_EXCEPTIONS = CALENDAR_PAGE + 0x14;
static final int CALENDAR_EXCEPTION_IS_DELETED = CALENDAR_PAGE + 0x15;
static final int CALENDAR_EXCEPTION_START_TIME = CALENDAR_PAGE + 0x16;
static final int CALENDAR_LOCATION = CALENDAR_PAGE + 0x17;
static final int CALENDAR_MEETING_STATUS = CALENDAR_PAGE + 0x18;
static final int CALENDAR_ORGANIZER_EMAIL = CALENDAR_PAGE + 0x19;
static final int CALENDAR_ORGANIZER_NAME = CALENDAR_PAGE + 0x1A;
static final int CALENDAR_RECURRENCE = CALENDAR_PAGE + 0x1B;
static final int CALENDAR_RECURRENCE_TYPE = CALENDAR_PAGE + 0x1C;
static final int CALENDAR_RECURRENCE_UNTIL = CALENDAR_PAGE + 0x1D;
static final int CALENDAR_RECURRENCE_OCCURRENCES = CALENDAR_PAGE + 0x1E;
static final int CALENDAR_RECURRENCE_INTERVAL = CALENDAR_PAGE + 0x1F;
static final int CALENDAR_RECURRENCE_DAYOFWEEK = CALENDAR_PAGE + 0x20;
static final int CALENDAR_RECURRENCE_DAYOFMONTH = CALENDAR_PAGE + 0x21;
static final int CALENDAR_RECURRENCE_WEEKOFMONTH = CALENDAR_PAGE + 0x22;
static final int CALENDAR_RECURRENCE_MONTHOFYEAR = CALENDAR_PAGE + 0x23;
static final int CALENDAR_REMINDER_MINS_BEFORE = CALENDAR_PAGE + 0x24;
static final int CALENDAR_SENSITIVITY = CALENDAR_PAGE + 0x25;
static final int CALENDAR_SUBJECT = CALENDAR_PAGE + 0x26;
static final int CALENDAR_START_TIME = CALENDAR_PAGE + 0x27;
static final int CALENDAR_UID = CALENDAR_PAGE + 0x28;
static final int CALENDAR_ATTENDEE_STATUS = CALENDAR_PAGE + 0x29;
static final int CALENDAR_ATTENDEE_TYPE = CALENDAR_PAGE + 0x2A;
static final int FOLDER_PAGE = FOLDER << PAGE_SHIFT;
static final int FOLDER_FOLDERS = FOLDER_PAGE + 5;
static final int FOLDER_FOLDER = FOLDER_PAGE + 6;
static final int FOLDER_DISPLAY_NAME = FOLDER_PAGE + 7;
static final int FOLDER_SERVER_ID = FOLDER_PAGE + 8;
static final int FOLDER_PARENT_ID = FOLDER_PAGE + 9;
static final int FOLDER_TYPE = FOLDER_PAGE + 0xA;
static final int FOLDER_RESPONSE = FOLDER_PAGE + 0xB;
static final int FOLDER_STATUS = FOLDER_PAGE + 0xC;
static final int FOLDER_CONTENT_CLASS = FOLDER_PAGE + 0xD;
static final int FOLDER_CHANGES = FOLDER_PAGE + 0xE;
static final int FOLDER_ADD = FOLDER_PAGE + 0xF;
static final int FOLDER_DELETE = FOLDER_PAGE + 0x10;
static final int FOLDER_UPDATE = FOLDER_PAGE + 0x11;
static final int FOLDER_SYNC_KEY = FOLDER_PAGE + 0x12;
static final int FOLDER_FOLDER_CREATE = FOLDER_PAGE + 0x13;
static final int FOLDER_FOLDER_DELETE= FOLDER_PAGE + 0x14;
static final int FOLDER_FOLDER_UPDATE = FOLDER_PAGE + 0x15;
static final int FOLDER_FOLDER_SYNC = FOLDER_PAGE + 0x16;
static final int FOLDER_COUNT = FOLDER_PAGE + 0x17;
static final int FOLDER_VERSION = FOLDER_PAGE + 0x18;
static final int EMAIL_PAGE = EMAIL << PAGE_SHIFT;
static final int EMAIL_ATTACHMENT = EMAIL_PAGE + 5;
static final int EMAIL_ATTACHMENTS = EMAIL_PAGE + 6;
static final int EMAIL_ATT_NAME = EMAIL_PAGE + 7;
static final int EMAIL_ATT_SIZE = EMAIL_PAGE + 8;
static final int EMAIL_ATT0ID = EMAIL_PAGE + 9;
static final int EMAIL_ATT_METHOD = EMAIL_PAGE + 0xA;
static final int EMAIL_ATT_REMOVED = EMAIL_PAGE + 0xB;
static final int EMAIL_BODY = EMAIL_PAGE + 0xC;
static final int EMAIL_BODY_SIZE = EMAIL_PAGE + 0xD;
static final int EMAIL_BODY_TRUNCATED = EMAIL_PAGE + 0xE;
static final int EMAIL_DATE_RECEIVED = EMAIL_PAGE + 0xF;
static final int EMAIL_DISPLAY_NAME = EMAIL_PAGE + 0x10;
static final int EMAIL_DISPLAY_TO = EMAIL_PAGE + 0x11;
static final int EMAIL_IMPORTANCE = EMAIL_PAGE + 0x12;
static final int EMAIL_MESSAGE_CLASS = EMAIL_PAGE + 0x13;
static final int EMAIL_SUBJECT = EMAIL_PAGE + 0x14;
static final int EMAIL_READ = EMAIL_PAGE + 0x15;
static final int EMAIL_TO = EMAIL_PAGE + 0x16;
static final int EMAIL_CC = EMAIL_PAGE + 0x17;
static final int EMAIL_FROM = EMAIL_PAGE + 0x18;
static final int EMAIL_REPLY_TO = EMAIL_PAGE + 0x19;
static final int EMAIL_ALL_DAY_EVENT = EMAIL_PAGE + 0x1A;
static final int EMAIL_CATEGORIES = EMAIL_PAGE + 0x1B;
static final int EMAIL_CATEGORY = EMAIL_PAGE + 0x1C;
static final int EMAIL_DTSTAMP = EMAIL_PAGE + 0x1D;
static final int EMAIL_END_TIME = EMAIL_PAGE + 0x1E;
static final int EMAIL_INSTANCE_TYPE = EMAIL_PAGE + 0x1F;
static final int EMAIL_INTD_BUSY_STATUS = EMAIL_PAGE + 0x20;
static final int EMAIL_LOCATION = EMAIL_PAGE + 0x21;
static final int EMAIL_MEETING_REQUEST = EMAIL_PAGE + 0x22;
static final int EMAIL_ORGANIZER = EMAIL_PAGE + 0x23;
static final int EMAIL_RECURRENCE_ID = EMAIL_PAGE + 0x24;
static final int EMAIL_REMINDER = EMAIL_PAGE + 0x25;
static final int EMAIL_RESPONSE_REQUESTED = EMAIL_PAGE + 0x26;
static final int EMAIL_RECURRENCES = EMAIL_PAGE + 0x27;
static final int EMAIL_RECURRENCE = EMAIL_PAGE + 0x28;
static final int EMAIL_RECURRENCE_TYPE = EMAIL_PAGE + 0x29;
static final int EMAIL_RECURRENCE_UNTIL = EMAIL_PAGE + 0x2A;
static final int EMAIL_RECURRENCE_OCCURRENCES = EMAIL_PAGE + 0x2B;
static final int EMAIL_RECURRENCE_INTERVAL = EMAIL_PAGE + 0x2C;
static final int EMAIL_RECURRENCE_DAYOFWEEK = EMAIL_PAGE + 0x2D;
static final int EMAIL_RECURRENCE_DAYOFMONTH = EMAIL_PAGE + 0x2E;
static final int EMAIL_RECURRENCE_WEEKOFMONTH = EMAIL_PAGE + 0x2F;
static final int EMAIL_RECURRENCE_MONTHOFYEAR = EMAIL_PAGE + 0x30;
static final int EMAIL_START_TIME = EMAIL_PAGE + 0x31;
static final int EMAIL_SENSITIVITY = EMAIL_PAGE + 0x32;
static final int EMAIL_TIME_ZONE = EMAIL_PAGE + 0x33;
static final int EMAIL_GLOBAL_OBJID = EMAIL_PAGE + 0x34;
static final int EMAIL_THREAD_TOPIC = EMAIL_PAGE + 0x35;
static final int EMAIL_MIME_DATA = EMAIL_PAGE + 0x36;
static final int EMAIL_MIME_TRUNCATED = EMAIL_PAGE + 0x37;
static final int EMAIL_MIME_SIZE = EMAIL_PAGE + 0x38;
static final int EMAIL_INTERNET_CPID = EMAIL_PAGE + 0x39;
static final int EMAIL_FLAG = EMAIL_PAGE + 0x3A;
static final int EMAIL_FLAG_STATUS = EMAIL_PAGE + 0x3B;
static final int EMAIL_CONTENT_CLASS = EMAIL_PAGE + 0x3C;
static final int EMAIL_FLAG_TYPE = EMAIL_PAGE + 0x3D;
static final int EMAIL_COMPLETE_TIME = EMAIL_PAGE + 0x3E;
static final int MOVE_PAGE = MOVE << PAGE_SHIFT;
static final int MOVE_MOVE_ITEMS = MOVE_PAGE + 5;
static final int MOVE_MOVE = MOVE_PAGE + 6;
static final int MOVE_SRCMSGID = MOVE_PAGE + 7;
static final int MOVE_SRCFLDID = MOVE_PAGE + 8;
static final int MOVE_DSTFLDID = MOVE_PAGE + 9;
static final int MOVE_RESPONSE = MOVE_PAGE + 0xA;
static final int MOVE_STATUS = MOVE_PAGE + 0xB;
static final int MOVE_DSTMSGID = MOVE_PAGE + 0xC;
static final int CONTACTS2_PAGE = CONTACTS2 << PAGE_SHIFT;
static final int CONTACTS2_CUSTOMER_ID = CONTACTS2_PAGE + 5;
static final int CONTACTS2_GOVERNMENT_ID = CONTACTS2_PAGE + 6;
static final int CONTACTS2_IM_ADDRESS = CONTACTS2_PAGE + 7;
static final int CONTACTS2_IM_ADDRESS_2 = CONTACTS2_PAGE + 8;
static final int CONTACTS2_IM_ADDRESS_3 = CONTACTS2_PAGE + 9;
static final int CONTACTS2_MANAGER_NAME = CONTACTS2_PAGE + 0xA;
static final int CONTACTS2_COMPANY_MAIN_PHONE = CONTACTS2_PAGE + 0xB;
static final int CONTACTS2_ACCOUNT_NAME = CONTACTS2_PAGE + 0xC;
static final int CONTACTS2_NICKNAME = CONTACTS2_PAGE + 0xD;
static final int CONTACTS2_MMS = CONTACTS2_PAGE + 0xE;
// The Ping constants are used by EasSyncService, and need to be public
static final int PING_PAGE = PING << PAGE_SHIFT;
public static final int PING_PING = PING_PAGE + 5;
public static final int PING_AUTD_STATE = PING_PAGE + 6;
public static final int PING_STATUS = PING_PAGE + 7;
public static final int PING_HEARTBEAT_INTERVAL = PING_PAGE + 8;
public static final int PING_FOLDERS = PING_PAGE + 9;
public static final int PING_FOLDER = PING_PAGE + 0xA;
public static final int PING_ID = PING_PAGE + 0xB;
public static final int PING_CLASS = PING_PAGE + 0xC;
public static final int PING_MAX_FOLDERS = PING_PAGE + 0xD;
static final int BASE_PAGE = BASE << PAGE_SHIFT;
static final int BASE_BODY_PREFERENCE = BASE_PAGE + 5;
static final int BASE_TYPE = BASE_PAGE + 6;
static final int BASE_TRUNCATION_SIZE = BASE_PAGE + 7;
static final int BASE_ALL_OR_NONE = BASE_PAGE + 8;
static final int BASE_RESERVED = BASE_PAGE + 9;
static final int BASE_BODY = BASE_PAGE + 0xA;
static final int BASE_DATA = BASE_PAGE + 0xB;
static final int BASE_ESTIMATED_DATA_SIZE = BASE_PAGE + 0xC;
static final int BASE_TRUNCATED = BASE_PAGE + 0xD;
static final int BASE_ATTACHMENTS = BASE_PAGE + 0xE;
static final int BASE_ATTACHMENT = BASE_PAGE + 0xF;
static final int BASE_DISPLAY_NAME = BASE_PAGE + 0x10;
static final int BASE_FILE_REFERENCE = BASE_PAGE + 0x11;
static final int BASE_METHOD = BASE_PAGE + 0x12;
static final int BASE_CONTENT_ID = BASE_PAGE + 0x13;
static final int BASE_CONTENT_LOCATION = BASE_PAGE + 0x14;
static final int BASE_IS_INLINE = BASE_PAGE + 0x15;
static final int BASE_NATIVE_BODY_TYPE = BASE_PAGE + 0x16;
static final int BASE_CONTENT_TYPE = BASE_PAGE + 0x17;
static public String[][] pages = {
{ // 0x00 AirSync
"Sync", "Responses", "Add", "Change", "Delete", "Fetch", "SyncKey", "ClientId",
"ServerId", "Status", "Collection", "Class", "Version", "CollectionId", "GetChanges",
"MoreAvailable", "WindowSize", "Commands", "Options", "FilterType", "Truncation",
"RTFTruncation", "Conflict", "Collections", "ApplicationData", "DeletesAsMoves",
"NotifyGUID", "Supported", "SoftDelete", "MIMESupport", "MIMETruncation", "Wait",
"Limit", "Partial"
},
{
// 0x01 Contacts
"Anniversary", "AssistantName", "AssistantTelephoneNumber", "Birthday", "Body",
"BodySize", "BodyTruncated", "Business2TelephoneNumber", "BusinessAddressCity",
"BusinessAddressCountry", "BusinessAddressPostalCode", "BusinessAddressState",
"BusinessAddressStreet", "BusinessFaxNumber", "BusinessTelephoneNumber",
"CarTelephoneNumber", "ContactsCategories", "ContactsCategory", "Children", "Child",
"CompanyName", "Department", "Email1Address", "Email2Address", "Email3Address",
"FileAs", "FirstName", "Home2TelephoneNumber", "HomeAddressCity", "HomeAddressCountry",
"HomeAddressPostalCode", "HomeAddressState", "HomeAddressStreet", "HomeFaxNumber",
"HomeTelephoneNumber", "JobTitle", "LastName", "MiddleName", "MobileTelephoneNumber",
"OfficeLocation", "OfficeAddressCity", "OfficeAddressCountry",
"OfficeAddressPostalCode", "OfficeAddressState", "OfficeAddressStreet", "PagerNumber",
"RadioTelephoneNumber", "Spouse", "Suffix", "Title", "Webpage", "YomiCompanyName",
"YomiFirstName", "YomiLastName", "CompressedRTF", "Picture"
},
{
// 0x02 Email
"Attachment", "Attachments", "AttName", "AttSize", "Add0Id", "AttMethod", "AttRemoved",
"Body", "BodySize", "BodyTruncated", "DateReceived", "DisplayName", "DisplayTo",
"Importance", "MessageClass", "Subject", "Read", "To", "CC", "From", "ReplyTo",
"AllDayEvent", "Categories", "Category", "DTStamp", "EndTime", "InstanceType",
"IntDBusyStatus", "Location", "MeetingRequest", "Organizer", "RecurrenceId", "Reminder",
"ResponseRequested", "Recurrences", "Recurence", "Recurrence_Type", "Recurrence_Until",
"Recurrence_Occurrences", "Recurrence_Interval", "Recurrence_DayOfWeek",
"Recurrence_DayOfMonth", "Recurrence_WeekOfMonth", "Recurrence_MonthOfYear",
"StartTime", "Sensitivity", "TimeZone", "GlobalObjId", "ThreadTopic", "MIMEData",
"MIMETruncated", "MIMESize", "InternetCPID", "Flag", "FlagStatus", "ContentClass",
"FlagType", "CompleteTime"
},
{
// 0x03 AirNotify
},
{
// 0x04 Calendar
"CalTimeZone", "CalAllDayEvent", "CalAttendees", "CalAttendee", "CalAttendee_Email",
"CalAttendee_Name", "CalBody", "CalBodyTruncated", "CalBusyStatus", "CalCategories",
"CalCategory", "CalCompressed_RTF", "CalDTStamp", "CalEndTime", "CalExeption",
"CalExceptions", "CalException_IsDeleted", "CalException_StartTime", "CalLocation",
"CalMeetingStatus", "CalOrganizer_Email", "CalOrganizer_Name", "CalRecurrence",
"CalRecurrence_Type", "CalRecurrence_Until", "CalRecurrence_Occurrences",
"CalRecurrence_Interval", "CalRecurrence_DayOfWeek", "CalRecurrence_DayOfMonth",
"CalRecurrence_WeekOfMonth", "CalRecurrence_MonthOfYear", "CalReminder_MinsBefore",
"CalSensitivity", "CalSubject", "CalStartTime", "CalUID", "CalAttendee_Status",
"CalAttendee_Type"
},
{
// 0x05 Move
"MoveItems", "Move", "SrcMsgId", "SrcFldId", "DstFldId", "Response", "Status",
"DstMsgId"
},
{
// 0x06 ItemEstimate
},
{
// 0x07 FolderHierarchy
"Folders", "Folder", "FolderDisplayName", "FolderServerId", "FolderParentId", "Type",
"Response", "Status", "ContentClass", "Changes", "FolderAdd", "FolderDelete",
"FolderUpdate", "FolderSyncKey", "FolderCreate", "FolderDelete", "FolderUpdate",
"FolderSync", "Count", "Version"
},
{
// 0x08 MeetingResponse
},
{
// 0x09 Tasks
},
{
// 0x0A ResolveRecipients
},
{
// 0x0B ValidateCert
},
{
// 0x0C Contacts2
"CustomerId", "GovernmentId", "IMAddress", "IMAddress2", "IMAddress3", "ManagerName",
"CompanyMainPhone", "AccountName", "NickName", "MMS"
},
{
// 0x0D Ping
"Ping", "AutdState", "Status", "HeartbeatInterval", "PingFolders", "PingFolder",
"PingId", "PingClass", "MaxFolders"
},
{
// 0x0E Provision
"Provision", "Policies", "Policy", "PolicyType", "PolicyKey", "Data", "Status",
"RemoteWipe", "EASProvidionDoc", "DevicePasswordEnabled",
"AlphanumericDevicePasswordRequired",
"DeviceEncryptionEnabled", "-unused-", "AttachmentsEnabled", "MinDevicePasswordLength",
"MaxInactivityTimeDeviceLock", "MaxDevicePasswordFailedAttempts", "MaxAttachmentSize",
"AllowSimpleDevicePassword", "DevicePasswordExpiration", "DevicePasswordHistory",
"AllowStorageCard", "AllowCamera", "RequireDeviceEncryption",
"AllowUnsignedApplications", "AllowUnsignedInstallationPackages",
"MinDevicePasswordComplexCharacters", "AllowWiFi", "AllowTextMessaging",
"AllowPOPIMAPEmail", "AllowBluetooth", "AllowIrDA", "RequireManualSyncWhenRoaming",
"AllowDesktopSync",
"MaxCalendarAgeFilder", "AllowHTMLEmail", "MaxEmailAgeFilder",
"MaxEmailBodyTruncationSize", "MaxEmailHTMLBodyTruncationSize",
"RequireSignedSMIMEMessages", "RequireEncryptedSMIMEMessages",
"RequireSignedSMIMEAlgorithm", "RequireEncryptionSMIMEAlgorithm",
"AllowSMIMEEncryptionAlgorithmNegotiation", "AllowSMIMESoftCerts", "AllowBrowser",
"AllowConsumerEmail", "AllowRemoteDesktop", "AllowInternetSharing",
"UnapprovedInROMApplicationList", "ApplicationName", "ApprovedApplicationList", "Hash"
},
{
// 0x0F Search
},
{
// 0x10 Gal
"DisplayName", "Phone", "Office", "Title", "Company", "Alias", "FirstName", "LastName",
"HomePhone", "MobilePhone", "EmailAddress"
},
{
// 0x11 AirSyncBase
"BodyPreference", "BodyPreferenceType", "BodyPreferenceTruncationSize", "AllOrNone",
"Body", "Data", "EstimatedDataSize", "Truncated", "Attachments", "Attachment",
"DisplayName", "FileReference", "Method", "ContentId", "ContentLocation", "IsInline",
"NativeBodyType", "ContentType"
},
{
// 0x12 Settings
},
{
// 0x13 DocumentLibrary
},
{
// 0x14 ItemOperations
}
};
}

View File

@ -18,7 +18,7 @@
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE. */
package com.android.exchange;
package com.android.exchange.adapter;
/** contains the WBXML constants */

View File

@ -1,4 +1,3 @@
/* Copyright (c) 2002,2003, Stefan Haustein, Oberhausen, Rhld., Germany
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
@ -22,7 +21,7 @@
//Contributors: Jonathan Cox, Bogdan Onoiu, Jerry Tian
//Simplified for Google, Inc. by Marc Blank
package com.android.exchange;
package com.android.exchange.adapter;
import java.io.*;
import java.util.*;
@ -31,6 +30,7 @@ import org.xmlpull.v1.*;
/**
* A class for writing WBXML.
*

View File

@ -11,7 +11,7 @@
* @author rob@iharder.net
* @version 2.2.2
*/
package com.android.exchange;
package com.android.exchange.utility;
import java.io.Writer;

View File

@ -15,8 +15,13 @@
* limitations under the License.
*/
package com.android.exchange;
package com.android.exchange.utility;
/**
* Encode and decode QuotedPrintable text, according to the specification. Since the Email
* application already does this elsewhere, the goal would be to use its functionality here.
*
*/
public class QuotedPrintable {
static public String toString (String str) {
int len = str.length();
@ -34,7 +39,9 @@ public class QuotedPrintable {
if (n == '\n') {
continue;
} else {
System.err.println("Not valid QP");
// This isn't valid QuotedPrintable, but what to do?
// Let's just ignore it because 1) it's extremely unlikely to
// happen, and 2) an exception is frankly no better.
}
} else {
// Must be less than 0x80, right?

View File

@ -15,7 +15,7 @@
* limitations under the License.
*/
package com.android.exchange;
package com.android.exchange.utility;
import java.io.File;
import java.io.FileInputStream;
@ -38,6 +38,12 @@ import android.text.Html;
import android.text.SpannedString;
import android.util.Log;
/**
* Generates RFC822 formatted message data from a Message object. This functionality is also needed
* by the SMTP code, so we should use a single piece of code for this purpose. stadler is currently
* planning on rewriting SMTP code to handle this task, and we will use that code when it is ready.
*
*/
public class Rfc822Formatter {
static final SimpleDateFormat rfc822DateFormat = new SimpleDateFormat("dd MMM yy HH:mm:ss Z");
@ -49,14 +55,14 @@ public class Rfc822Formatter {
static final String CRLF = "\r\n";
static public String writeEmailAsRfc822String (Context context, Account acct,
static public String writeEmailAsRfc822String(Context context, Account acct,
Message msg, String uniqueId) throws IOException {
StringWriter w = new StringWriter();
writeEmailAsRfc822(context, acct, msg, w, uniqueId);
return w.toString();
}
static public boolean writeEmailAsRfc822 (Context context, Account acct,
static public boolean writeEmailAsRfc822(Context context, Account acct,
Message msg, Writer writer, String uniqueId) throws IOException {
// For now, multi-part alternative means an HTML reply...
boolean alternativeParts = false;