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:
parent
7c3cca80a0
commit
b6493a07ef
|
@ -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>
|
||||
|
|
|
@ -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)"
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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...");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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 */
|
||||
|
|
@ -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.
|
||||
*
|
|
@ -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;
|
||||
|
|
@ -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?
|
|
@ -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;
|
Loading…
Reference in New Issue