Initial submission for EAS support (email)

* There is some temporary code ensuring that SyncManager runs (this will be removed when Exchange
is entirely independent)
* The service interface (aidl) is in place for most user-facing actions (load more, start sync, etc.)
* EAS account validation is done via aidl
* Folder and message sync appear to be functional in this early submission
* Provider now does cascading deletes (Account -> HostAuth and Mailbox, Mailbox -> Message,
  Message -> Attachment and Body)
This commit is contained in:
Marc Blank 2009-06-16 12:03:45 -07:00
parent 553603337a
commit 2c67f1f8b8
33 changed files with 8334 additions and 108 deletions

View File

@ -163,6 +163,11 @@
android:enabled="false"
>
</service>
<service
android:name="com.android.exchange.SyncManager"
android:enabled="true"
>
</service>
<provider
android:name=".provider.AttachmentProvider"
android:authorities="com.android.email.attachmentprovider"

View File

@ -19,6 +19,7 @@ package com.android.email.activity;
import com.android.email.Email;
import com.android.email.provider.EmailContent;
import com.android.email.provider.EmailContent;
import com.android.exchange.SyncManager;
//import com.android.exchange.SyncManager;
import android.app.Activity;
@ -50,7 +51,7 @@ public class Welcome extends Activity {
// TODO Automatically start Exchange service, until we can base this on the existence of
// at least one Exchange account
//startService(new Intent(this, SyncManager.class));
startService(new Intent(this, SyncManager.class));
// Find out how many accounts we have, and if there's just one, go directly to it
Cursor c = null;

View File

@ -20,10 +20,11 @@ public class AuthenticationFailedException extends MessagingException {
public static final long serialVersionUID = -1;
public AuthenticationFailedException(String message) {
super(message);
super(MessagingException.AUTHENTICATION_FAILED, message);
}
public AuthenticationFailedException(String message, Throwable throwable) {
super(message, throwable);
}
mExceptionType = MessagingException.AUTHENTICATION_FAILED;
}
}

View File

@ -28,6 +28,7 @@ package com.android.email.mail;
public class MessagingException extends Exception {
public static final long serialVersionUID = -1;
public static final int NO_ERROR = -1;
/** Any exception that does not specify a specific issue */
public static final int UNSPECIFIED_EXCEPTION = 0;
/** Connection or IO errors */
@ -38,10 +39,11 @@ public class MessagingException extends Exception {
public static final int AUTH_REQUIRED = 3;
/** General security failures */
public static final int GENERAL_SECURITY = 4;
/** Authentication failed */
public static final int AUTHENTICATION_FAILED = 5;
private final int mExceptionType;
protected int mExceptionType;
public MessagingException(String message) {
super(message);
mExceptionType = UNSPECIFIED_EXCEPTION;

View File

@ -17,10 +17,18 @@
package com.android.email.mail.exchange;
import com.android.email.Email;
import com.android.email.mail.AuthenticationFailedException;
import com.android.email.mail.MessagingException;
import com.android.exchange.ISyncManager;
import com.android.exchange.SyncManager;
//import com.android.exchange.EasService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.RemoteException;
import android.text.TextUtils;
import java.net.URI;
@ -107,13 +115,51 @@ public class ExchangeTransportExample {
* @param uri the server/account to try and connect to
* @throws MessagingException thrown if the connection, server, account are not useable
*/
ISyncManager mSyncManagerService = null;
private ServiceConnection mSyncManagerConnection = new ServiceConnection () {
public void onServiceConnected(ComponentName name, IBinder binder) {
mSyncManagerService = ISyncManager.Stub.asInterface(binder);
}
public void onServiceDisconnected(ComponentName name) {
mSyncManagerService = null;
}
};
public void checkSettings(URI uri) throws MessagingException {
setUri(uri);
// Perform a server connection here
// Throw MessageException if not useable
// TODO EasService isn't part of the commit
//boolean ssl = uri.getScheme().contains("ssl+");
//EasService.validate(EasService.class, mHost, mUsername, mPassword, ssl ? 443 : 80, ssl, mContext);
boolean ssl = uri.getScheme().contains("ssl+");
try {
mContext.bindService(new Intent(mContext, SyncManager.class), mSyncManagerConnection, Context.BIND_AUTO_CREATE);
// TODO Is this the right way of waiting for a connection??
int count = 0;
while ((count++ < 10) && (mSyncManagerService == null)) {
Thread.sleep(500);
}
if (mSyncManagerService == null) {
throw new MessagingException(MessagingException.UNSPECIFIED_EXCEPTION);
}
// The result of validate is a MessagingException error code
// We use NO_ERROR to indicate a validated account
int result = mSyncManagerService.validate("eas", mHost, mUsername, mPassword, ssl ? 443 : 80, ssl);
if (result != MessagingException.NO_ERROR) {
if (result == MessagingException.AUTHENTICATION_FAILED) {
throw new AuthenticationFailedException("Authentication failed.");
} else {
throw new MessagingException(result);
}
}
} catch (RemoteException e) {
throw new MessagingException("Call to validate generated an exception", e);
} catch (InterruptedException e) {
} finally {
if (mSyncManagerService != null) {
mContext.unbindService(mSyncManagerConnection);
} else
throw new MessagingException(MessagingException.UNSPECIFIED_EXCEPTION);
}
}
/**

View File

@ -194,7 +194,10 @@ public abstract class EmailContent {
}
public static final class Body extends EmailContent implements BodyColumns {
public static final String TABLE_NAME = "Body";
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/body");
public static final int CONTENT_ID_COLUMN = 0;
public static final int CONTENT_MESSAGE_KEY_COLUMN = 1;
public static final int CONTENT_HTML_CONTENT_COLUMN = 2;
@ -226,14 +229,7 @@ public abstract class EmailContent {
mBaseUri = CONTENT_URI;
}
/**
* The content:// style URL for this table
*/
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/body");
public static final String TABLE_NAME = "Body";
@Override
@Override
public ContentValues toContentValues() {
ContentValues values = new ContentValues();
@ -354,6 +350,9 @@ 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 Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/message");
public static final String KEY_TIMESTAMP_DESC = MessageColumns.TIMESTAMP + " desc";
@ -489,12 +488,6 @@ public abstract class EmailContent {
mBaseUri = CONTENT_URI;
}
public static final String TABLE_NAME = "Message";
public static final String UPDATES_TABLE_NAME = "Message_Updates";
/**
* The content:// style URL for this table
*/
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/message");
public static final Uri UPDATED_CONTENT_URI =
Uri.parse(EmailContent.CONTENT_URI + "/updatedMessage");
@ -735,6 +728,9 @@ public abstract class EmailContent {
}
public static final class Account extends EmailContent implements AccountColumns, Parcelable {
public static final String TABLE_NAME = "Account";
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/account");
public final static int FLAGS_NOTIFY_NEW_MAIL = 1;
public final static int FLAGS_VIBRATE = 2;
@ -811,13 +807,6 @@ public abstract class EmailContent {
mCompatibilityUuid = UUID.randomUUID().toString();
}
public static final String TABLE_NAME = "Account";
/**
* The content:// style URL for this table
*/
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/account");
public static Account restoreAccountWithId(Context context, long id) {
Uri u = ContentUris.withAppendedId(Account.CONTENT_URI, id);
Cursor c = context.getContentResolver().query(u, Account.CONTENT_PROJECTION,
@ -1433,6 +1422,9 @@ public abstract class EmailContent {
}
public static final class Attachment extends EmailContent implements AttachmentColumns {
public static final String TABLE_NAME = "Attachment";
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/attachment");
public String mFileName;
public String mMimeType;
@ -1464,14 +1456,7 @@ public abstract class EmailContent {
public Attachment() {
mBaseUri = CONTENT_URI;
}
public static final String TABLE_NAME = "Attachment";
/**
* The content:// style URL for this table
*/
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/attachment");
/**
* Restore an Attachment from the database, given its unique id
* @param context
@ -1522,6 +1507,47 @@ public abstract class EmailContent {
values.put(AttachmentColumns.ENCODING, mEncoding);
return values;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
// mBaseUri is not parceled
dest.writeLong(mId);
dest.writeString(mFileName);
dest.writeString(mMimeType);
dest.writeLong(mSize);
dest.writeString(mContentId);
dest.writeString(mContentUri);
dest.writeLong(mMessageKey);
dest.writeString(mLocation);
dest.writeString(mEncoding);
}
public Attachment(Parcel in) {
mBaseUri = EmailContent.Attachment.CONTENT_URI;
mId = in.readLong();
mFileName = in.readString();
mMimeType = in.readString();
mSize = in.readLong();
mContentId = in.readString();
mContentUri = in.readString();
mMessageKey = in.readLong();
mLocation = in.readString();
mEncoding = in.readString();
}
public static final Parcelable.Creator<EmailContent.Attachment> CREATOR
= new Parcelable.Creator<EmailContent.Attachment>() {
public EmailContent.Attachment createFromParcel(Parcel in) {
return new EmailContent.Attachment(in);
}
public EmailContent.Attachment[] newArray(int size) {
return new EmailContent.Attachment[size];
}
};
}
public interface MailboxColumns {
@ -1557,6 +1583,9 @@ public abstract class EmailContent {
}
public static final class Mailbox extends EmailContent implements SyncColumns, MailboxColumns {
public static final String TABLE_NAME = "Mailbox";
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/mailbox");
public String mDisplayName;
public String mServerId;
public String mParentServerId;
@ -1602,17 +1631,12 @@ public abstract class EmailContent {
mBaseUri = CONTENT_URI;
}
public static final String TABLE_NAME = "Mailbox";
/**
* The content:// style URL for this table
*/
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/mailbox");
// Types of mailboxes. The list is ordered to match a typical UI presentation, e.g.
// placing the inbox at the top.
// The "main" mailbox for the account, almost always referred to as "Inbox"
public static final int TYPE_INBOX = 0;
// Types of mailboxes
// Holds mail (generic)
public static final int TYPE_MAIL = 1;
// Parent-only mailbox; holds no mail
@ -1718,6 +1742,9 @@ public abstract class EmailContent {
}
public static final class HostAuth extends EmailContent implements HostAuthColumns, Parcelable {
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;
@ -1758,14 +1785,7 @@ public abstract class EmailContent {
mPort = -1;
}
public static final String TABLE_NAME = "HostAuth";
/**
* The content:// style URL for this table
*/
public static final Uri CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/hostauth");
/**
/**
* Restore a HostAuth from the database, given its unique id
* @param context
* @param id

View File

@ -48,13 +48,6 @@ import android.util.Log;
import java.util.ArrayList;
/*
* TODO
*
* Add Email.Body class and support, now that this is stored separately
* Handle deletion cascades (either w/ triggers or code)
*
*/
public class EmailProvider extends ContentProvider {
private static final String TAG = "EmailProvider";
@ -221,25 +214,14 @@ public class EmailProvider extends ContentProvider {
+ " on " + Message.TABLE_NAME + " (" + MessageColumns.MAILBOX_KEY + ");");
db.execSQL("create index message_" + SyncColumns.SERVER_ID
+ " on " + Message.TABLE_NAME + " (" + SyncColumns.SERVER_ID + ");");
// When a record is FIRST updated, copy the original data into the updates table
// Server version not null tells us that this is synced back to the server
// The sync engine can determine what needs to go up to the server
db.execSQL("CREATE TRIGGER message_update UPDATE ON " + Message.TABLE_NAME +
" WHEN old." + SyncColumns.DIRTY_COUNT + "=0 AND new." + SyncColumns.DIRTY_COUNT +
"!=0 AND old." + SyncColumns.SERVER_VERSION + " IS NOT NULL " +
"BEGIN INSERT INTO " + Message.UPDATES_TABLE_NAME +
" SELECT * FROM message WHERE " +
EmailContent.RECORD_ID + "=old." + EmailContent.RECORD_ID + ";END");
// Deleted records are automatically copied into updates table
// TODO How will the sync adapter know that these records are deletions?
// Answer: WeDo we may have to use an EXCEPT clause, as in
// SELECT id from Message_Update where mailboxKey=n EXCEPT SELECT id from Message?
db.execSQL("CREATE TRIGGER message_delete BEFORE DELETE ON " + Message.TABLE_NAME +
" BEGIN INSERT INTO " + Message.UPDATES_TABLE_NAME +
" SELECT * FROM message WHERE " + EmailContent.RECORD_ID +
"=old." + EmailContent.RECORD_ID + ";END");
// Deleting a Message deletes associated Attachments
// Deleting the associated Body cannot be done in a trigger, because the Body is stored
// in a separate database, and trigger cannot operate on attached databases.
db.execSQL("create trigger message_delete before delete on " + Message.TABLE_NAME +
" begin delete from " + Attachment.TABLE_NAME +
" where " + AttachmentColumns.MESSAGE_KEY + "=old." + EmailContent.RECORD_ID +
"; end");
}
static void upgradeMessageTable(SQLiteDatabase db, int oldVersion, int newVersion) {
@ -264,7 +246,14 @@ public class EmailProvider extends ContentProvider {
+ AccountColumns.RINGTONE_URI + " text "
+ ");";
db.execSQL("create table " + Account.TABLE_NAME + s);
}
// Deleting an account deletes associated Mailboxes and HostAuth's
db.execSQL("create trigger account_delete before delete on " + Account.TABLE_NAME +
" begin delete from " + Mailbox.TABLE_NAME +
" where " + MailboxColumns.ACCOUNT_KEY + "=old." + EmailContent.RECORD_ID +
"; delete from " + HostAuth.TABLE_NAME +
" where " + HostAuthColumns.ACCOUNT_KEY + "=old." + EmailContent.RECORD_ID +
"; end");
}
static void upgradeAccountTable(SQLiteDatabase db, int oldVersion, int newVersion) {
try {
@ -318,7 +307,11 @@ public class EmailProvider extends ContentProvider {
+ " on " + Mailbox.TABLE_NAME + " (" + MailboxColumns.SERVER_ID + ")");
db.execSQL("create index mailbox_" + MailboxColumns.ACCOUNT_KEY
+ " on " + Mailbox.TABLE_NAME + " (" + MailboxColumns.ACCOUNT_KEY + ")");
// Deleting a Mailbox deletes associated Messages
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");
}
static void upgradeMailboxTable(SQLiteDatabase db, int oldVersion, int newVersion) {
@ -453,37 +446,81 @@ public class EmailProvider extends ContentProvider {
Context context = getContext();
SQLiteDatabase db = (match >= BODY_BASE) ? getBodyDatabase(context) : getDatabase(context);
int table = match >> BASE_SHIFT;
String id;
String id = "0";
boolean checkDeleteBody = false;
if (Config.LOGV) {
Log.v(TAG, "EmailProvider.delete: uri=" + uri + ", match is " + match);
}
int result;
switch (match) {
case BODY_ID:
case MESSAGE_ID:
case UPDATED_MESSAGE_ID:
case ATTACHMENT_ID:
case MAILBOX_ID:
case ACCOUNT_ID:
case HOSTAUTH_ID:
id = uri.getPathSegments().get(1);
result = db.delete(TABLE_NAMES[table], whereWithId(id, selection), selectionArgs);
break;
case BODY:
case MESSAGE:
case UPDATED_MESSAGE:
case ATTACHMENT:
case MAILBOX:
case ACCOUNT:
case HOSTAUTH:
result = db.delete(TABLE_NAMES[table], selection, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
try {
switch (match) {
// These are cases in which one or more Messages might get deleted, either by
// cascade or explicitly
case MAILBOX_ID:
case MAILBOX:
case ACCOUNT_ID:
case ACCOUNT:
case MESSAGE:
case MESSAGE_ID:
// Handle lost Body records here, since this cannot be done in a trigger
// The process is:
// 1) Attach the Body database
// 2) Begin a transaction, ensuring that both databases are affected atomically
// 3) Do the requested deletion, with cascading deletions handled in triggers
// 4) End the transaction, committing all changes atomically
// 5) Detach the Body database
String bodyFileName = context.getDatabasePath(BODY_DATABASE_NAME)
.getAbsolutePath();
db.execSQL("attach \"" + bodyFileName + "\" as BodyDatabase");
db.beginTransaction();
if (match != MESSAGE_ID) {
checkDeleteBody = true;
}
break;
}
switch (match) {
case BODY_ID:
case MESSAGE_ID:
case UPDATED_MESSAGE_ID:
case ATTACHMENT_ID:
case MAILBOX_ID:
case ACCOUNT_ID:
case HOSTAUTH_ID:
id = uri.getPathSegments().get(1);
result = db.delete(TABLE_NAMES[table], whereWithId(id, selection),
selectionArgs);
break;
case BODY:
case MESSAGE:
case UPDATED_MESSAGE:
case ATTACHMENT:
case MAILBOX:
case ACCOUNT:
case HOSTAUTH:
result = db.delete(TABLE_NAMES[table], selection, selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
if (checkDeleteBody) {
// Delete any orphaned Body records
db.execSQL("delete from " + Body.TABLE_NAME + " where _id in (select _id from " +
Body.TABLE_NAME + " except select _id from " + Message.TABLE_NAME + ")");
db.setTransactionSuccessful();
} else if (match == MESSAGE_ID) {
// Delete the Body record associated with the deleted message
db.execSQL("delete from " + Body.TABLE_NAME + " where _id=" + id);
db.setTransactionSuccessful();
}
} finally {
if (checkDeleteBody) {
db.endTransaction();
db.execSQL("detach BodyDatabase");
}
}
getContext().getContentResolver().notifyChange(uri, null);
return result;
}

View File

@ -16,7 +16,7 @@
package com.android.email.service;
import com.android.email.MessagingController;
import com.android.exchange.SyncManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@ -26,6 +26,8 @@ public class BootReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
MailService.actionReschedule(context);
// TODO Remove when Exchange is running on its own
context.startService(new Intent(context, SyncManager.class));
}
else if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(intent.getAction())) {
MailService.actionCancel(context);

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,352 @@
/*
* 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.email.provider.EmailContent;
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 EmailContent.Account mAccount;
private EasService mService;
private ContentResolver mContentResolver;
private Context mContext;
private EmailContent.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(EmailContent.Message.CONTENT_URI,
EmailContent.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<EmailContent.Message> emails) throws IOException {
EmailContent.Message msg = new EmailContent.Message();
String to = "";
String from = "";
String cc = "";
String replyTo = "";
int size = 0;
msg.mAccountKey = mAccount.mId;
msg.mMailboxKey = mMailbox.mId;
msg.mFlagLoaded = EmailContent.Message.LOADED;
ArrayList<EmailContent.Attachment> atts = new ArrayList<EmailContent.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<EmailContent.Attachment> atts, EmailContent.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) {
EmailContent.Attachment att = new EmailContent.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(EmailContent.Message.CONTENT_URI,
EmailContent.Message.ID_COLUMN_PROJECTION,
EmailContent.SyncColumns.SERVER_ID + "=" + serverId, null, null);
try {
if (c.moveToFirst()) {
mService.log("Deleting " + serverId);
deletes.add(c.getLong(EmailContent.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(EmailContent.Message.CONTENT_URI,
EmailContent.Message.LIST_PROJECTION,
EmailContent.SyncColumns.SERVER_ID + "=?", bindArgument, null);
try {
if (c.moveToFirst()) {
mService.log("Changing " + serverId);
oldRead = c.getInt(EmailContent.Message.LIST_READ_COLUMN) ==
EmailContent.Message.READ;
id = c.getLong(EmailContent.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<EmailContent.Message> newEmails = new ArrayList<EmailContent.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 (EmailContent.Message content: newEmails) {
content.addSaveOps(ops);
}
for (Long id: deletedEmails) {
ops.add(ContentProviderOperation
.newDelete(ContentUris.withAppendedId(EmailContent.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(EmailContent.SyncColumns.DIRTY_COUNT, 0);
cv.put(EmailContent.MessageColumns.FLAG_READ, true);
for (Long id: changedEmails) {
// For now, don't handle read->unread
ops.add(ContentProviderOperation.newUpdate(ContentUris
.withAppendedId(EmailContent.Message.CONTENT_URI, id)).withValues(cv).build());
}
}
ops.add(ContentProviderOperation.newUpdate(ContentUris
.withAppendedId(EmailContent.Mailbox.CONTENT_URI, mMailbox.mId))
.withValues(mMailbox.toContentValues()).build());
try {
ContentProviderResult[] results = mService.mContext.getContentResolver()
.applyBatch(EmailProvider.EMAIL_AUTHORITY, ops);
for (ContentProviderResult result: results) {
if (result.uri == null) {
Log.v(TAG, "Null result in ContentProviderResult!");
}
}
} catch (RemoteException e) {
// There is nothing to be done here; fail by returning null
} catch (OperationApplicationException e) {
// There is nothing to be done here; fail by returning null
}
Log.v(TAG, "Mailbox EOS syncKey now: " + mMailbox.mSyncKey);
}
}

View File

@ -0,0 +1,215 @@
/*
* 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.email.provider.EmailContent;
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 EmailContent.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<EmailContent.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)) {
EmailContent.Mailbox m = new EmailContent.Mailbox();
m.mDisplayName = name;
m.mServerId = serverId;
m.mAccountKey = mAccount.mId;
if (type == INBOX_TYPE) {
m.mSyncFrequency = EmailContent.Account.CHECK_INTERVAL_PUSH;
m.mType = EmailContent.Mailbox.TYPE_INBOX;
} else if (type == OUTBOX_TYPE) {
//m.mSyncFrequency = MailService.OUTBOX_FREQUENCY;
m.mSyncFrequency = EmailContent.Account.CHECK_INTERVAL_NEVER;
m.mType = EmailContent.Mailbox.TYPE_OUTBOX;
} else {
if (type == SENT_TYPE) {
m.mType = EmailContent.Mailbox.TYPE_SENT;
} else if (type == DRAFTS_TYPE) {
m.mType = EmailContent.Mailbox.TYPE_DRAFTS;
} else if (type == DELETED_TYPE) {
m.mType = EmailContent.Mailbox.TYPE_TRASH;
}
m.mSyncFrequency = EmailContent.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<EmailContent.Mailbox> newBoxes = new ArrayList<EmailContent.Mailbox>();
while (nextTag(EasTags.FOLDER_CHANGES) != END) {
if (tag == EasTags.FOLDER_ADD) {
addParser(newBoxes);
} else if (tag == EasTags.FOLDER_COUNT) {
getValueInt();
} else
skipTag();
}
for (EmailContent.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 (EmailContent.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 (EmailContent.Mailbox content: newBoxes) {
ContentProviderOperation.Builder b = ContentProviderOperation
.newInsert(EmailContent.Mailbox.CONTENT_URI);
b.withValues(content.toContentValues());
ops.add(b.build());
}
ops.add(ContentProviderOperation.newUpdate(ContentUris
.withAppendedId(EmailContent.Account.CONTENT_URI, mAccount.mId))
.withValues(mAccount.toContentValues()).build());
try {
ContentProviderResult[] results = mService.mContext.getContentResolver()
.applyBatch(EmailProvider.EMAIL_AUTHORITY, ops);
for (ContentProviderResult result: results) {
if (result.uri == null) {
return;
}
}
Log.v(TAG, "New syncKey: " + mAccount.mSyncKey);
} catch (RemoteException e) {
// There is nothing to be done here; fail by returning null
} catch (OperationApplicationException e) {
// There is nothing to be done here; fail by returning null
}
}
}
}

View File

@ -0,0 +1,59 @@
/*
* 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 android.util.Log;
import com.android.email.provider.EmailContent;
public class EasMoveParser extends EasParser {
private static final String TAG = "EasMoveParser";
private EasService mService;
private EmailContent.Mailbox mMailbox;
protected boolean mMoreAvailable = false;
public EasMoveParser(InputStream in, EasService service) throws IOException {
super(in);
mService = service;
mMailbox = service.mMailbox;
setDebug(true);
}
public void parse() throws IOException {
int status;
if (nextTag(START_DOCUMENT) != EasTags.MOVE_MOVE_ITEMS)
throw new IOException();
while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
if (tag == EasTags.MOVE_RESPONSE) {
// Ignore
} else if (tag == EasTags.MOVE_STATUS) {
status = getValueInt();
if (status != 3) {
Log.e(TAG, "Sync failed (3 is success): " + status);
}
} else if (tag == EasTags.SYNC_RESPONSES) {
skipTag();
} else
skipTag();
}
mMailbox.save(mService.mContext);
}
}

View File

@ -0,0 +1,83 @@
/*
* 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.net.HttpURLConnection;
import com.android.email.provider.EmailContent;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
public class EasOutboxService extends EasService {
public EasOutboxService(Context _context, EmailContent.Mailbox _mailbox) {
super(_context, _mailbox);
mContext = _context;
EmailContent.HostAuth ha =
EmailContent.HostAuth.restoreHostAuthWithId(mContext, mAccount.mHostAuthKeyRecv);
mHostAddress = ha.mAddress;
mUserName = ha.mLogin;
mPassword = ha.mPassword;
}
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(EmailContent.Message.CONTENT_URI,
EmailContent.Message.CONTENT_PROJECTION, "mMailbox=" + mMailbox, null, null);
try {
if (c.moveToFirst()) {
EmailContent.Message msg = new EmailContent.Message().restore(c);
if (msg != null) {
String data = Rfc822Formatter
.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...");
mContext.getContentResolver().delete(ContentUris.withAppendedId(
EmailContent.Message.CONTENT_URI, msg.mId), null, null);
} else {
ContentValues cv = new ContentValues();
cv.put("uid", 1);
EmailContent.Message.update(mContext,
EmailContent.Message.CONTENT_URI, msg.mId, cv);
//intent.putExtra("text", "WHOA! Your message with subject \"" + msg.mSubject + "\" failed to send.");
}
//mContext.sendBroadcast(intent);
updateUI();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
c.close();
}
} catch (RuntimeException e1) {
e1.printStackTrace();
}
}
}

View File

@ -0,0 +1,297 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to The Android Open Source Project.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.exchange;
import java.io.*;
import java.util.ArrayList;
import android.content.Context;
import android.util.Log;
public abstract class EasParser {
private static final String TAG = "EasParser";
public static final int START_DOCUMENT = 0;
public static final int DONE = 1;
public static final int START = 2;
public static final int END = 3;
public static final int TEXT = 4;
public static final int END_DOCUMENT = 3;
private static final int NOT_FETCHED = Integer.MIN_VALUE;
private static final int NOT_ENDED = Integer.MIN_VALUE;
private static final int EOF_BYTE = -1;
private boolean debug = false;
private boolean capture = false;
private ArrayList<Integer> captureArray;
private InputStream in;
private int depth;
private int nextId = NOT_FETCHED;
private String[] tagTable;
private String[][] tagTables = new String[24][];
private String[] nameArray = new String[32];
private int[] tagArray = new int[32];
private boolean noContent;
// Available to all to avoid method calls
public int endTag = NOT_ENDED;
public int type;
public int tag;
public String name;
public String text;
public int num;
public void parse () throws IOException {
}
public EasParser (InputStream in) throws IOException {
String[][] pages = EasTags.pages;
for (int i = 0; i < pages.length; i++) {
String[] page = pages[i];
if (page.length > 0) {
setTagTable(i, page);
}
}
setInput(in);
}
public void setDebug (boolean val) {
debug = val;
}
public void captureOn () {
capture = true;
captureArray = new ArrayList<Integer>();
}
public void captureOff (Context context, String file) {
try {
FileOutputStream out = context.openFileOutput(file, Context.MODE_WORLD_WRITEABLE);
out.write(captureArray.toString().getBytes());
out.close();
} catch (FileNotFoundException e) {
} catch (IOException e) {
}
}
public String getValue () throws IOException {
getNext(false);
String val = text;
getNext(false);
if (type != END) {
throw new IOException("No END found!");
}
endTag = tag;
return val;
}
public int getValueInt () throws IOException {
getNext(true);
int val = num;
getNext(false);
if (type != END) {
throw new IOException("No END found!");
}
endTag = tag;
return val;
}
public int nextTag (int endTag) throws IOException {
while (getNext(false) != DONE) {
if (type == START) {
return tag;
} else if (type == END && tag == endTag) {
return END;
}
}
if (endTag == START_DOCUMENT) {
return END_DOCUMENT;
}
throw new EodException();
}
public void skipTag () throws IOException {
int thisTag = tag;
while (getNext(false) != DONE) {
if (type == END && tag == thisTag) {
return;
}
}
throw new EofException();
}
public int nextToken() throws IOException {
getNext(false);
return type;
}
public void setInput(InputStream in) throws IOException {
this.in = in;
readByte(); // version
readInt(); // ?
readInt(); // 106 (UTF-8)
readInt(); // string table length
tagTable = tagTables[0];
}
public int next () throws IOException {
getNext(false);
return type;
}
private final int getNext(boolean asInt) throws IOException {
if (type == END) {
depth--;
} else {
endTag = NOT_ENDED;
}
if (noContent) {
type = END;
noContent = false;
return type;
}
text = null;
name = null;
int id = nextId ();
while (id == Wbxml.SWITCH_PAGE) {
nextId = NOT_FETCHED;
tagTable = tagTables[(readByte())];
id = nextId();
}
nextId = NOT_FETCHED;
switch (id) {
case -1 :
type = DONE;
break;
case Wbxml.END :
type = END;
if (debug) {
name = nameArray[depth];
Log.v(TAG, "</" + name + '>');
}
tag = endTag = tagArray[depth];
break;
case Wbxml.STR_I :
type = TEXT;
if (asInt) {
num = readInlineInt();
} else {
text = readInlineString();
}
if (debug) {
Log.v(TAG, asInt ? Integer.toString(num) : text);
}
break;
default :
type = START;
tag = id & 0x3F;
noContent = (id & 0x40) == 0;
depth++;
if (debug) {
name = tagTable[tag - 5];
Log.v(TAG, '<' + name + '>');
nameArray[depth] = name;
}
tagArray[depth] = tag;
}
return type;
}
private int read () throws IOException {
int i = in.read();
if (capture) {
captureArray.add(i);
}
return i;
}
private int nextId () throws IOException {
if (nextId == NOT_FETCHED) {
nextId = read();
}
return nextId;
}
private int readByte() throws IOException {
int i = read();
if (i == EOF_BYTE) {
throw new EofException();
}
return i;
}
private int readInlineInt() throws IOException {
int result = 0;
while (true) {
int i = readByte();
if (i == 0) {
return result;
}
if (i >= '0' && i <= '9') {
result = (result * 10) + (i - '0');
} else {
throw new IOException("Non integer");
}
}
}
private int readInt() throws IOException {
int result = 0;
int i;
do {
i = readByte();
result = (result << 7) | (i & 0x7f);
} while ((i & 0x80) != 0);
return result;
}
private String readInlineString() throws IOException {
StringBuilder sb = new StringBuilder(4096);
while (true){
int i = read();
if (i == 0) {
break;
} else if (i == EOF_BYTE) {
throw new EofException();
}
sb.append((char)i);
}
String res = sb.toString();
return res;
}
public void setTagTable(int page, String[] table) {
tagTables[page] = table;
}
}

View File

@ -0,0 +1,22 @@
/*
* 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 EasParserException extends Exception {
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to The Android Open Source Project.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.exchange;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import com.android.email.provider.EmailContent;
import android.content.Context;
public class EasPingService extends EasService {
EasService mCaller;
HttpURLConnection mConnection = null;
public EasPingService(Context _context, EmailContent.Mailbox _mailbox, EasService _caller) {
super(_context, _mailbox);
mCaller = _caller;
mHostAddress = _caller.mHostAddress;
mUserName = _caller.mUserName;
mPassword = _caller.mPassword;
}
class EASPingParser extends EasParser {
protected boolean mMoreAvailable = false;
public EASPingParser(InputStream in, EasService service) throws IOException {
super(in);
mMailbox = service.mMailbox;
setDebug(true);
}
public void parse() throws IOException {
int status;
if (nextTag(START_DOCUMENT) != EasTags.PING_PING) {
throw new IOException();
}
while (nextTag(START_DOCUMENT) != END_DOCUMENT) {
if (tag == EasTags.PING_STATUS) {
status = getValueInt();
log("Ping completed, status = " + status);
if (status == 1 || status == 2) {
}
mCaller.ping();
} else {
skipTag();
}
}
}
}
public void stop () {
mConnection.disconnect();
}
public void run () {
try {
EASSerializer s = new EASSerializer();
s.start("Ping").data("HeartbeatInterval", "900").start("PingFolders")
.start("PingFolder").data("PingId", mMailbox.mServerId).data("PingClass", "Email")
.end("PingFolder").end("PingFolders").end("Ping").end();
String data = s.toString();
HttpURLConnection uc = sendEASPostCommand("Ping", data);
mConnection = uc;
log("Sending ping, read timeout: " + uc.getReadTimeout() / 1000 + "s");
int code = uc.getResponseCode();
log("Response code: " + code);
if (code == HttpURLConnection.HTTP_OK) {
String encoding = uc.getHeaderField("Transfer-Encoding");
if (encoding == null) {
int len = uc.getHeaderFieldInt("Content-Length", 0);
if (len > 0) {
new EASPingParser(uc.getInputStream(), this).parse();
}
}
}
} catch (IOException e1) {
e1.printStackTrace();
} catch (RuntimeException e1) {
e1.printStackTrace();
}
mCaller.ping();
log(Thread.currentThread().getName() + " thread completed...");
}
}

View File

@ -0,0 +1,798 @@
/*
* 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.email.provider.EmailContent;
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, EmailContent.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; //EmailContent.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(EmailContent.AttachmentColumns.CONTENT_URI, f.getAbsolutePath());
cv.put(EmailContent.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(EmailContent.Message.CONTENT_URI, EmailContent.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);
EmailContent.HostAuth ha = EmailContent.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 != EmailContent.Account.CHECK_INTERVAL_PUSH) {
return;
}
// Handle push here...
Thread pingThread = null;
EasPingService pingService = new EasPingService(mContext, mMailbox, this);
runAsleep(10*MINS);
synchronized (mWaitTarget) {
mIsIdle = true;
try {
log("Wait...");
pingThread = new Thread(pingService);
pingThread.setName("Ping " + pingThread.getId());
log("Starting thread " + pingThread.getName());
pingThread.start();
mWaitTarget.wait(14*MINS);
} catch (InterruptedException e) {
} finally {
runAwake();
}
log("Wait terminated.");
if (pingThread != null && pingThread.isAlive()) {
// Make the ping service stop, one way or another
log("Stopping " + pingThread.getName());
pingService.stop();
pingThread.interrupt();
}
mIsIdle = false;
}
} catch (IOException e) {
log("IOException: " + e.getMessage());
//logException(e);
}
}
} catch (Exception e) {
log("Exception: " + e.getMessage());
//logException(e);
} finally {
log("EAS sync finished.");
//MailService.done(this);
}
}
}

View File

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

View File

@ -0,0 +1,21 @@
/*
* 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;
parcelable EmailContent.Attachment;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,24 @@
/*
* 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 EodException extends IOException {
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (C) 2008-2009 Marc Blank
* Licensed to The Android Open Source Project.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.exchange;
import java.io.IOException;
public class EofException extends IOException {
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,41 @@
/*
* 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 com.android.exchange.ISyncManagerCallback;
import com.android.exchange.EmailContent;
interface ISyncManager {
int validate(in String protocol, in String host, in String userName, in String password, int port, boolean ssl) ;
void registerCallback(ISyncManagerCallback cb);
void unregisterCallback(ISyncManagerCallback cb);
boolean startSync(long mailboxId);
boolean stopSync(long mailboxId);
boolean updateFolderList(long accountId);
boolean loadMore(long messageId, ISyncManagerCallback cb);
boolean loadAttachment(long messageId, in EmailContent.Attachment att, ISyncManagerCallback cb);
boolean createFolder(long accountId, String name);
boolean deleteFolder(long accountId, String name);
boolean renameFolder(long accountId, String oldName, String newName);
//AddressLookup - real-time address lookup (EAS)
}

View File

@ -0,0 +1,22 @@
/*
* 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;
oneway interface ISyncManagerCallback {
void progress(int value);
}

View File

@ -0,0 +1,36 @@
/*
/*
* 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.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
public class KeepAliveReceiver 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);
}
}
}

View File

@ -0,0 +1,55 @@
/*
* 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;
/**
* MockParserStream is an InputStream that feeds pre-generated data into various EasParser
* subclasses.
*
* After parsing is done, the result can be obtained with getResult
*
*/
public class MockParserStream extends InputStream {
int[] array;
int pos = 0;
Object value;
MockParserStream (int[] _array) {
array = _array;
}
@Override
public int read() throws IOException {
try {
return array[pos++];
} catch (IndexOutOfBoundsException e) {
throw new IOException("End of stream");
}
}
public void setResult(Object _value) {
value = _value;
}
public Object getResult() {
return value;
}
}

View File

@ -0,0 +1,46 @@
/*
* 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 com.android.email.provider.EmailContent;
import android.os.Handler;
public class PartRequest {
public long id;
public long emailId;
public EmailContent.Attachment att;
public String loc;
public long size;
public long loaded;
public Handler handler;
public PartRequest (long _emailId, EmailContent.Attachment _att) {
id = System.currentTimeMillis();
emailId = _emailId;
att = _att;
loc = att.mLocation;
size = att.mSize;
loaded = 0;
}
public PartRequest (long _emailId, EmailContent.Attachment _att, Handler _handler) {
this(_emailId, _att);
handler = _handler;
}
}

View File

@ -0,0 +1,242 @@
/*
* 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.email.provider.EmailContent;
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 EmailContent.Mailbox mMailbox;
protected long mMailboxId;
protected Thread mThread;
protected String mMailboxName;
protected EmailContent.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, EmailContent.Mailbox _mailbox) {
mContext = _context;
mMailbox = _mailbox;
mMailboxId = _mailbox.mId;
mMailboxName = _mailbox.mServerId;
mAccount = EmailContent.Account.restoreAccountWithId(_context, _mailbox.mAccountKey);
}
// Will be required when subclasses are instantiated by name
public ProtocolService (String prefix) {
}
public abstract void validateAccount (String host, String userName, String password,
int port, boolean ssl, Context context) throws MessagingException;
static public void validate (Class<? extends ProtocolService> klass, String host,
String userName, String password, int port, boolean ssl, Context context)
throws MessagingException {
ProtocolService svc;
try {
svc = klass.newInstance();
svc.validateAccount(host, userName, password, port, ssl, context);
} catch (IllegalAccessException e) {
throw new MessagingException("internal error", e);
} catch (InstantiationException e) {
throw new MessagingException("internal error", e);
}
}
public static class ValidationResult {
static final int NO_FAILURE = 0;
static final int CONNECTION_FAILURE = 1;
static final int VALIDATION_FAILURE = 2;
static final int EXCEPTION = 3;
static final ValidationResult succeeded = new ValidationResult(true, NO_FAILURE, null);
boolean success;
int failure = NO_FAILURE;
String reason = null;
Exception exception = null;
ValidationResult (boolean _success, int _failure, String _reason) {
success = _success;
failure = _failure;
reason = _reason;
}
ValidationResult (boolean _success) {
success = _success;
}
ValidationResult (Exception e) {
success = false;
failure = EXCEPTION;
exception = e;
}
public boolean isSuccess () {
return success;
}
public String getReason () {
return reason;
}
}
public final void runAwake () {
//MailService.runAwake(mMailboxId);
}
public final void runAsleep (long millis) {
//MailService.runAsleep(mMailboxId, millis);
}
// Common call used by the various protocols to send a "mail" message to the UI
protected void updateUI () {
}
protected void log (String str) {
if (Email.DEBUG) {
Log.v(Email.LOG_TAG, str);
}
}
// Delay until there is some kind of network connectivity
// Subclasses should allow some number of retries before failing, and kicking the ball back to MailService
public int waitForConnectivity () {
ConnectivityManager cm = (ConnectivityManager)mContext
.getSystemService(Context.CONNECTIVITY_SERVICE);
while (true) {
NetworkInfo info = cm.getActiveNetworkInfo();
if (info != null && info.isConnected()) {
DetailedState state = info.getDetailedState();
if (state == DetailedState.CONNECTED) {
return info.getType();
} else {
// TODO Happens sometimes; find out why...
log("Not quite connected? Pause 1 second");
}
pause(1000);
} else {
log("Not connected; waiting 15 seconds");
pause(NETWORK_WAIT);
}
}
}
// Convenience
private void pause (int ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
}
}
// PartRequest handling (common functionality)
// Can be overridden if desired, but IMAP/EAS both use the next three methods as-is
public void addPartRequest (PartRequest req) {
synchronized(mPartRequests) {
mPartRequests.add(req);
}
}
public void removePartRequest (PartRequest req) {
synchronized(mPartRequests) {
mPartRequests.remove(req);
}
}
public PartRequest hasPartRequest(long emailId, String part) {
synchronized(mPartRequests) {
for (PartRequest pr: mPartRequests) {
if (pr.emailId == emailId && pr.loc.equals(part))
return pr;
}
}
return null;
}
// CancelPartRequest is sent in response to user input to stop a request (attachment load at this point)
// that is in progress. This will almost certainly require code overriding the base functionality, as
// sockets may need to be closed, etc. and this functionality will be service dependent. This returns
// the canceled PartRequest or null
public PartRequest cancelPartRequest(long emailId, String part) {
synchronized(mPartRequests) {
PartRequest p = null;
for (PartRequest pr: mPartRequests) {
if (pr.emailId == emailId && pr.loc.equals(part)) {
p = pr;
break;
}
}
if (p != null) {
mPartRequests.remove(p);
return p;
}
}
return null;
}
}

View File

@ -0,0 +1,126 @@
/*
* 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 QuotedPrintable {
static public String toString (String str) {
int len = str.length();
// Make sure we don't get an index out of bounds error with the = character
int max = len - 2;
StringBuilder sb = new StringBuilder(len);
try {
for (int i = 0; i < len; i++) {
char c = str.charAt(i);
if (c == '=') {
if (i < max) {
char n = str.charAt(++i);
if (n == '\r') {
n = str.charAt(++i);
if (n == '\n') {
continue;
} else {
System.err.println("Not valid QP");
}
} else {
// Must be less than 0x80, right?
int a;
if (n >= '0' && n <= '9') {
a = (n - '0') << 4;
} else {
a = (10 + (n - 'A')) << 4;
}
n = str.charAt(++i);
if (n >= '0' && n <= '9') {
c = (char) (a + (n - '0'));
} else {
c = (char) (a + 10 + (n - 'A'));
}
}
} if (i + 1 == len) {
continue;
}
}
sb.append(c);
}
} catch (IndexOutOfBoundsException e) {
}
String ret = sb.toString();
return ret;
}
static public String encode (String str) {
int len = str.length();
StringBuffer sb = new StringBuffer(len + len>>2);
int i = 0;
while (i < len) {
char c = str.charAt(i++);
if (c < 0x80) {
sb.append(c);
} else {
sb.append('&');
sb.append('#');
sb.append((int)c);
sb.append(';');
}
}
return sb.toString();
}
static public int decode (byte[] bytes, int len) {
// Make sure we don't get an index out of bounds error with the = character
int max = len - 2;
int pos = 0;
try {
for (int i = 0; i < len; i++) {
char c = (char)bytes[i];
if (c == '=') {
if (i < max) {
char n = (char)bytes[++i];
if (n == '\r') {
n = (char)bytes[++i];
if (n == '\n') {
continue;
} else {
System.err.println("Not valid QP");
}
} else {
// Must be less than 0x80, right?
int a;
if (n >= '0' && n <= '9') {
a = (n - '0') << 4;
} else {
a = (10 + (n - 'A')) << 4;
}
n = (char)bytes[++i];
if (n >= '0' && n <= '9') {
c = (char) (a + (n - '0'));
} else {
c = (char) (a + 10 + (n - 'A'));
}
}
} if (i + 1 > len) {
continue;
}
}
bytes[pos++] = (byte)c;
}
} catch (IndexOutOfBoundsException e) {
}
return pos;
}
}

View File

@ -0,0 +1,258 @@
/*
* 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.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Date;
import com.android.email.provider.EmailContent;
import android.content.ContentUris;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.text.Html;
import android.text.SpannedString;
import android.util.Log;
public class Rfc822Formatter {
static final SimpleDateFormat rfc822DateFormat = new SimpleDateFormat("dd MMM yy HH:mm:ss Z");
static final SimpleDateFormat friendlyDateFormat = new SimpleDateFormat("MMM dd, yyyy");
static final SimpleDateFormat friendlyTimeFormat = new SimpleDateFormat("hh:mm a");
static public final String HTML_DRAFT_HEADER = "<!--AMDraft-->";
static public final String HTML_REPLY_HEADER = "<!--AMReply-->";
static final String CRLF = "\r\n";
static public String writeEmailAsRfc822String (Context context, EmailContent.Account acct,
EmailContent.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, EmailContent.Account acct,
EmailContent.Message msg, Writer writer, String uniqueId) throws IOException {
// For now, multi-part alternative means an HTML reply...
boolean alternativeParts = false;
Uri u = ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI, msg.mId)
.buildUpon().appendPath("attachment").build();
Cursor c = context.getContentResolver().query(u, EmailContent.Attachment.CONTENT_PROJECTION,
null, null, null);
try {
if (c.moveToFirst())
Log.v("Rfc822", "Has attachments");
} finally {
c.close();
}
//**PROVIDER
boolean mixedParts = false; //(!msg.attachments.isEmpty());
EmailContent.Message reply = null;
boolean forward = false;
long referenceId = msg.mReferenceKey;
if (referenceId != 0) {
if (referenceId < 0) {
referenceId = 0 - referenceId;
forward = true;
}
reply = EmailContent.Message.restoreMessageWithId(context, referenceId);
alternativeParts = true;
}
String date = rfc822DateFormat.format(new Date());
String alternativeBoundary = null;
String mixedBoundary = null;
writeHeader(writer, "Date", date); //"6 Jan 09 12:34:55 PST");
String displayName = acct.mDisplayName;
if (displayName == null || displayName.length() == 0) {
writeHeader(writer, "From", acct.mEmailAddress);
} else
writeHeader(writer, "From", displayName + " <" + acct.mEmailAddress + '>');
writeHeader(writer, "Subject", msg.mSubject);
// TODO Be consistent with SMTP
writeHeader(writer, "Message-ID", "<" + (uniqueId == null ? "emu" : uniqueId) + '.' +
System.currentTimeMillis() + "@AndroidMail.com");
String to = msg.mTo;
writeHeaderAddress(writer, "To", to);
writeHeaderAddress(writer, "Cc", msg.mCc);
writeHeader(writer, "X-Mailer", "AndroidMail v0.3.x");
if (mixedParts || alternativeParts)
writeHeader(writer, "MIME-Version", "1.0");
if (mixedParts) {
mixedBoundary = "--=_AndroidMail_mixed_boundary_" + System.nanoTime();
writeHeader(writer, "Content-Type", "multipart/mixed; boundary=\""
+ mixedBoundary + "\"");
writer.write(CRLF);
}
if (alternativeParts) {
alternativeBoundary = "--=_AndroidMail_alternative_boundary_" + System.nanoTime();
writeHeader(writer, "Content-Type", "multipart/alternative; boundary=\""
+ alternativeBoundary + "\"");
writer.write(CRLF);
}
// Write text part
if (alternativeParts) {
writeBoundary(writer, alternativeBoundary, false);
} else if (mixedParts) {
writeBoundary(writer, mixedBoundary, false);
}
writeHeader(writer, "Content-Type", "text/plain; charset=us-ascii");
writeHeader(writer, "Content-Transfer-Encoding", "7bit");
writer.write(CRLF);
//String text = Messages.getBody(context, msg);
String text = "Fake body (for now)";
writeWithCRLF(writer, text);
if (alternativeParts) {
writeBoundary(writer, alternativeBoundary, false);
writeHeader(writer, "Content-Type", "text/html; charset=us-ascii");
writeHeader(writer, "Content-Transfer-Encoding", "quoted-printable");
writer.write(CRLF);
String html = Html.toHtml(new SpannedString(text));
writeWithCRLF(writer, HTML_DRAFT_HEADER);
writeWithCRLF(writer, html);
writeWithCRLF(writer, HTML_REPLY_HEADER);
if (reply != null) {
html = Html.toHtml(new SpannedString(forward ? createForwardIntro(reply) :
createReplyIntro(reply)));
writeWithCRLF(writer, html);
String replyText = "Body of reply text"; //Messages.getBody(context, reply);
if (msg.mHtmlInfo == null)
replyText = Html.toHtml(new SpannedString(replyText));
writeWithCRLF(writer, QuotedPrintable.encode(replyText));
}
writeBoundary(writer, alternativeBoundary, true);
}
if (mixedParts) {
for (EmailContent.Attachment att: msg.mAttachments) {
writeBoundary(writer, mixedBoundary, false);
writeHeader(writer, "Content-Type", att.mMimeType);
writeHeader(writer, "Content-Transfer-Encoding", "base64");
String name = att.mContentUri;
if (name == null || name.length() == 0)
name = "picture.jpg";
writeHeader(writer, "Content-Disposition", "attachment; filename=\"" + name + "\"");
writer.write(CRLF);
// Write content of attachment here...
String fn = att.mFileName;
Uri uri = Uri.parse(fn);
InputStream inputStream = null;
if (fn.startsWith("/")) {
File f = new File(fn);
if (f.exists())
inputStream = new FileInputStream(f);
}
else
inputStream = context.getContentResolver().openInputStream(uri);
if (inputStream != null) {
Base64.encodeInputStreamToWriter(inputStream, writer);
inputStream.close();
}
inputStream.close();
writer.write(CRLF);
}
writeBoundary(writer, mixedBoundary, true);
}
writer.flush();
return true;
}
protected static String createReplyIntro (EmailContent.Message msg) {
StringBuilder sb = new StringBuilder(2048);
Date d = new Date(msg.mTimeStamp);
sb.append("\n\n-----\nOn ");
sb.append(friendlyDateFormat.format(d));
sb.append(", at ");
sb.append(friendlyTimeFormat.format(d).toLowerCase());
sb.append(", ");
sb.append(msg.mSender);
sb.append(" wrote: \n\n");
return sb.toString();
}
protected static String createForwardIntro (EmailContent.Message msg) {
StringBuilder sb = new StringBuilder(2048);
sb.append("\n\nBegin forwarded message:\n\n");
sb.append("From: ");
sb.append(msg.mFrom);
sb.append("\nTo: ");
sb.append(msg.mTo);
sb.append("\nDate: ");
Date d = new Date(msg.mTimeStamp);
sb.append(friendlyDateFormat.format(d));
sb.append(", at ");
sb.append(friendlyTimeFormat.format(d).toLowerCase());
sb.append("\nSubject: ");
sb.append(msg.mSubject);
sb.append("\n\n");
return sb.toString();
}
private static void writeBoundary (Writer w, String boundary, boolean last) throws IOException {
w.write("--");
w.write(boundary);
if (last)
w.write("--");
w.write("\r\n");
}
private static void writeWithCRLF (Writer w, String stuff) throws IOException {
w.write(stuff);
w.write("\r\n");
}
private static void writeHeaderAddress (Writer w, String header, String addressList)
throws IOException {
if (addressList != null && addressList.length() > 0)
writeHeader(w, header, addressList);
}
private static void writeHeader (Writer w, String header, String value) throws IOException {
w.write(header);
w.write(": ");
w.write(value);
w.write("\r\n");
}
}

View File

@ -0,0 +1,888 @@
/*
* 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.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import com.android.email.mail.MessagingException;
import com.android.email.provider.EmailContent;
import com.android.exchange.EmailContent.Attachment;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.net.NetworkInfo.State;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
import android.os.PowerManager;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.PowerManager.WakeLock;
import android.preference.PreferenceManager;
import android.database.ContentObserver;
public class SyncManager extends Service implements Runnable {
public static final int AWAKE = 0;
public static final int SLEEP_WEEKEND = 1;
public static final int SLEEP_HOURS = 2;
public static final int OFFLINE = 3;
public static final int DEFAULT_WINDOW = Integer.MIN_VALUE;
public static final int SECS = 1000;
public static final int MINS = 60*SECS;
static SyncManager INSTANCE;
static int mStatus = AWAKE;
static boolean mToothpicks = false;
static Object mSyncToken = new Object();
static Thread mServiceThread = null;
HashMap<Long, ProtocolService> serviceMap = new HashMap<Long, ProtocolService> ();
boolean mStop = false;
SharedPreferences mSettings;
Handler mHandler = new Handler();
AccountObserver mAccountObserver;
MailboxObserver mMailboxObserver;
final RemoteCallbackList<ISyncManagerCallback> mCallbacks
= new RemoteCallbackList<ISyncManagerCallback>();
private final ISyncManager.Stub mBinder = new ISyncManager.Stub() {
public int validate(String protocol, String host, String userName, String password,
int port, boolean ssl) throws RemoteException {
try {
ProtocolService.validate(EasService.class, host, userName, password, port, ssl,
SyncManager.this);
return MessagingException.NO_ERROR;
} catch (MessagingException e) {
return e.getExceptionType();
}
}
public void registerCallback(ISyncManagerCallback cb) {
if (cb != null) mCallbacks.register(cb);
}
public void unregisterCallback(ISyncManagerCallback cb) {
if (cb != null) mCallbacks.unregister(cb);
}
public boolean startSync(long mailboxId) throws RemoteException {
// TODO Auto-generated method stub
return false;
}
public boolean stopSync(long mailboxId) throws RemoteException {
// TODO Auto-generated method stub
return false;
}
public boolean updateFolderList(long accountId) throws RemoteException {
// TODO Auto-generated method stub
return false;
}
public boolean loadMore(long messageId, ISyncManagerCallback cb) throws RemoteException {
// TODO Auto-generated method stub
return false;
}
public boolean createFolder(long accountId, String name) throws RemoteException {
// TODO Auto-generated method stub
return false;
}
public boolean deleteFolder(long accountId, String name) throws RemoteException {
// TODO Auto-generated method stub
return false;
}
public boolean renameFolder(long accountId, String oldName, String newName)
throws RemoteException {
// TODO Auto-generated method stub
return false;
}
public boolean loadAttachment(long messageId, Attachment att, ISyncManagerCallback cb)
throws RemoteException {
// TODO Auto-generated method stub
return false;
}
};
class AccountObserver extends ContentObserver {
ArrayList<Long> accountIds = new ArrayList<Long>();
public AccountObserver(Handler handler) {
super(handler);
// At startup, we want to see what accounts exist and cache them
Cursor c = getContentResolver().query(EmailContent.Account.CONTENT_URI, EmailContent.Account.CONTENT_PROJECTION, null, null, null);
try {
if (c.moveToFirst()) {
do {
accountIds.add(c.getLong(EmailContent.Account.CONTENT_ID_COLUMN));
} while (c.moveToNext());
}
} finally {
c.close();
}
for (long accountId: accountIds) {
Context context = getContext();
int cnt = EmailContent.Mailbox.count(context, EmailContent.Mailbox.CONTENT_URI, "accountKey=" + accountId, null);
if (cnt == 0) {
initializeAccount(accountId);
}
}
}
public void onChange (boolean selfChange) {
// A change to the list of accounts requires us to scan for deletions (so we can stop running syncs)
// At startup, we want to see what accounts exist and cache them
ArrayList<Long> currentIds = new ArrayList<Long>();
Cursor c = getContentResolver().query(EmailContent.Account.CONTENT_URI, EmailContent.Account.CONTENT_PROJECTION, null, null, null);
try {
if (c.moveToFirst()) {
do {
currentIds.add(c.getLong(EmailContent.Account.CONTENT_ID_COLUMN));
} while (c.moveToNext());
}
for (long accountId: accountIds) {
if (!currentIds.contains(accountId)) {
// This is a deletion; shut down any account-related syncs
accountDeleted(accountId);
}
}
for (long accountId: currentIds) {
if (!accountIds.contains(accountId)) {
// This is an addition; create our magic hidden mailbox...
initializeAccount(accountId);
}
}
} finally {
c.close();
}
// See if there's anything to do...
kick();
}
private void initializeAccount (long acctId) {
EmailContent.Account acct = EmailContent.Account.restoreAccountWithId(getContext(), acctId);
EmailContent.Mailbox main = new EmailContent.Mailbox();
main.mDisplayName = "_main";
main.mServerId = "_main";
main.mAccountKey = acct.mId;
main.mType = EmailContent.Mailbox.TYPE_MAIL;
main.mSyncFrequency = EmailContent.Account.CHECK_INTERVAL_PUSH;
main.mFlagVisible = false;
main.save(getContext());
INSTANCE.log("Initializing account: " + acct.mDisplayName);
}
private void accountDeleted (long acctId) {
synchronized (mSyncToken) {
List<Long> deletedBoxes = new ArrayList<Long>();
for (Long mid : INSTANCE.serviceMap.keySet()) {
EmailContent.Mailbox box = EmailContent.Mailbox.restoreMailboxWithId(INSTANCE, mid);
if (box != null) {
if (box.mAccountKey == acctId) {
ProtocolService svc = INSTANCE.serviceMap.get(mid);
if (svc != null) {
svc.stop();
svc.mThread.interrupt();
}
deletedBoxes.add(mid);
}
}
}
for (Long mid : deletedBoxes) {
INSTANCE.serviceMap.remove(mid);
}
}
}
}
class MailboxObserver extends ContentObserver {
public MailboxObserver(Handler handler) {
super(handler);
}
public void onChange (boolean selfChange) {
// See if there's anything to do...
kick();
}
}
@Override
public IBinder onBind(Intent arg0) {
return mBinder;
}
public void log (String str) {
Log.v("EmailApp:MailService", str);
}
@Override
public void onCreate () {
INSTANCE = this;
mAccountObserver = new AccountObserver(mHandler);
mMailboxObserver = new MailboxObserver(mHandler);
// Start our thread...
if (mServiceThread == null || !mServiceThread.isAlive()) {
log(mServiceThread == null ? "Starting thread..." : "Restarting thread...");
mServiceThread = new Thread(this, "<MailService>");
mServiceThread.start();
} else {
log("Attempt to start MailService though already started before?");
}
}
static private HashMap<Long, Boolean> mWakeLocks = new HashMap<Long, Boolean>();
static private HashMap<Long, PendingIntent> mPendingIntents = new HashMap<Long, PendingIntent>();
static private WakeLock mWakeLock = null;
static public void acquireWakeLock (long id) {
synchronized (mWakeLocks) {
Boolean lock = mWakeLocks.get(id);
if (lock == null) {
INSTANCE.log("+WakeLock requested for " + id);
if (mWakeLock == null) {
PowerManager pm = (PowerManager) INSTANCE.getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "MAIL_SERVICE");
mWakeLock.acquire();
INSTANCE.log("+WAKE LOCK ACQUIRED");
}
mWakeLocks.put(id, true);
}
}
}
static public void releaseWakeLock (long id) {
synchronized (mWakeLocks) {
Boolean lock = mWakeLocks.get(id);
if (lock != null) {
INSTANCE.log("+WakeLock not needed for " + id);
mWakeLocks.remove(id);
if (mWakeLocks.isEmpty()) {
mWakeLock.release();
mWakeLock = null;
INSTANCE.log("+WAKE LOCK RELEASED");
}
}
}
}
static private String alarmOwner (long id) {
if (id == -1) {
return "MailService";
}
else return "Mailbox " + Long.toString(id);
}
static private void clearAlarm (long id) {
synchronized (mPendingIntents) {
PendingIntent pi = mPendingIntents.get(id);
if (pi != null) {
AlarmManager alarmManager = (AlarmManager)INSTANCE.getSystemService(Context.ALARM_SERVICE);
alarmManager.cancel(pi);
INSTANCE.log("+Alarm cleared for " + alarmOwner(id));
mPendingIntents.remove(id);
}
}
}
static private void setAlarm (long id, long millis) {
synchronized (mPendingIntents) {
PendingIntent pi = mPendingIntents.get(id);
if (pi == null) {
Intent i = new Intent(INSTANCE, KeepAliveReceiver.class);
i.putExtra("mailbox", id);
i.setData(Uri.parse("Box" + id));
pi = PendingIntent.getBroadcast(INSTANCE, 0, i, 0);
mPendingIntents.put(id, pi);
AlarmManager alarmManager = (AlarmManager)INSTANCE.getSystemService(Context.ALARM_SERVICE);
alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + millis, pi);
INSTANCE.log("+Alarm set for " + alarmOwner(id) + ", " + millis + "ms");
}
}
}
static private void clearAlarms () {
AlarmManager alarmManager = (AlarmManager)INSTANCE.getSystemService(Context.ALARM_SERVICE);
synchronized (mPendingIntents) {
for (PendingIntent pi : mPendingIntents.values()) {
alarmManager.cancel(pi);
}
mPendingIntents.clear();
}
}
static public void runAwake (long id) {
acquireWakeLock(id);
clearAlarm(id);
}
static public void runAsleep (long id, long millis) {
setAlarm(id, millis);
releaseWakeLock(id);
}
static public void ping (long id) {
ProtocolService service = INSTANCE.serviceMap.get(id);
if (service != null) {
EmailContent.Mailbox m = EmailContent.Mailbox.restoreMailboxWithId(INSTANCE, id);
if (m != null) {
service.mAccount = EmailContent.Account.restoreAccountWithId(INSTANCE, m.mAccountKey);
service.mMailbox = m;
service.ping();
}
}
}
@Override
public void onDestroy () {
log("!!! MaiLService onDestroy");
if (mWakeLock != null) {
mWakeLock.release();
mWakeLock = null;
}
clearAlarms();
}
public class ConnectivityReceiver extends BroadcastReceiver {
@Override
public void onReceive (Context context, Intent intent) {
Bundle b = intent.getExtras();
if (b != null) {
NetworkInfo a = (NetworkInfo)b.get("networkInfo");
String info = "CM Info: " + a.getTypeName();
State state = a.getState();
if (state == State.CONNECTED) {
info += " CONNECTED";
} else if (state == State.CONNECTING) {
info += " CONNECTING";
} else if (state == State.DISCONNECTED) {
info += " DISCONNECTED";
} else if (state == State.DISCONNECTING) {
info += " DISCONNECTING";
} else if (state == State.SUSPENDED) {
info += " SUSPENDED";
} else if (state == State.UNKNOWN) {
info += " UNKNOWN";
}
log("CONNECTIVITY: " + info);
}
}
}
private void pause (int ms) {
try {
Thread.sleep(ms);
} catch (InterruptedException e) {
}
}
private void startService (ProtocolService service, EmailContent.Mailbox m) {
synchronized (mSyncToken) {
String mailboxName = m.mDisplayName;
String accountName = service.mAccount.mDisplayName;
Thread thread = new Thread(service, mailboxName + "(" + accountName + ")");
log("Starting thread for " + mailboxName + " in account " + accountName);
thread.start();
serviceMap.put(m.mId, service);
}
}
private void startService (EmailContent.Mailbox m) {
synchronized (mSyncToken) {
EmailContent.Account acct = EmailContent.Account.restoreAccountWithId(this, m.mAccountKey);
if (acct != null) {
ProtocolService service;
service = new EasService(this, m);
startService(service, m);
}
}
}
private void startSleep () {
synchronized (mSyncToken) {
// Shut everything down
boolean stoppedOne = false;
// Keep track of which services we've stopped
ArrayList<Long> toStop = new ArrayList<Long>();
// Shut down all of our running services
for (Long mid : serviceMap.keySet()) {
toStop.add(mid);
stoppedOne = true;
}
for (Long mid: toStop) {
ProtocolService svc = serviceMap.get(mid);
log("Going to sleep: shutting down " + svc.mAccount.mDisplayName + "/" + svc.mMailboxName);
svc.stop();
svc.mThread.interrupt();
stoppedOne = true;
}
// Remove the stopped services from the map
//for (Long mid : stopped)
// serviceMap.remove(mid);
// Let the UI know
if (stoppedOne) {
}
}
}
private void broadcastSleep () {
}
public void run () {
log("MailService: run");
Debug.waitForDebugger();
mStop = false;
runAwake(-1);
ContentResolver resolver = getContentResolver();
resolver.registerContentObserver(EmailContent.Account.CONTENT_URI, false, mAccountObserver);
resolver.registerContentObserver(EmailContent.Mailbox.CONTENT_URI, false, mMailboxObserver);
ConnectivityReceiver cr = new ConnectivityReceiver();
registerReceiver(cr, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
mSettings = PreferenceManager.getDefaultSharedPreferences(this);
GregorianCalendar calendar = new GregorianCalendar();
mStatus = AWAKE;
try {
while (!mStop) {
runAwake(-1);
log("%%MailService heartbeat");
while (!mStop) {
NetworkInfo info = cm.getActiveNetworkInfo();
if (info != null && info.isConnected()) {
break;
} else {
pause(10*SECS);
}
}
long nextWait = 10*MINS;
long now = System.currentTimeMillis();
// We can be MUCH smarter! We can send notices of sleep time changes and otherwise cache all of this...
long sleepHours = mSettings.getLong("sleep_hours", 0);
if (sleepHours != 0) {
boolean pastStartTime = false;
boolean beforeEndTime = false;
boolean wantSleep = false;
calendar.setTimeInMillis(now);
int nowHour = calendar.get(GregorianCalendar.HOUR_OF_DAY);
int nowMinute = calendar.get(GregorianCalendar.MINUTE);
long sleepStart = sleepHours >> 32;
int startHour = (int)(sleepStart / 100);
int startMinute = (int)(sleepStart % 100);
long sleepEnd = sleepHours & 0x00000000FFFFFFFFL;
int endHour = (int)(sleepEnd / 100);
int endMinute = (int)(sleepEnd % 100);
if (sleepStart > sleepEnd) {
if ((nowHour > startHour) || (nowHour == startHour && nowMinute >= startMinute) || (nowHour < endHour) || (nowHour == endHour && nowMinute <= endMinute))
wantSleep = true;
} else if (((startHour < nowHour || (startHour == nowHour && nowMinute >= startMinute)) && ((nowHour < endHour) || (nowHour == endHour && nowMinute <= endMinute))))
wantSleep = true;
if (wantSleep && (mStatus == AWAKE)) {
mStatus = SLEEP_HOURS;
log("Going to sleep now...");
log("startHour: " + startHour + ", startMinute: " + startMinute + ", endHour: " + endHour + ", endMinute: " + endMinute);
log("nowHour: " + nowHour + ", nowMinute: " + nowMinute);
startSleep();
broadcastSleep();
} else if (!wantSleep && (mStatus == SLEEP_HOURS)) {
mStatus = AWAKE;
log("Waking up now: " + (pastStartTime ? "pastStartTime, " : "not pastStartTime, ") + (beforeEndTime ? "beforeEndTime" : "not beforeEndTime"));
log("startHour: " + startHour + ", startMinute: " + startMinute + ", endHour: " + endHour + ", endMinute: " + endMinute);
log("nowHour: " + nowHour + ", nowMinute: " + nowMinute);
broadcastSleep();
}
}
boolean sleepWeekends = mSettings.getBoolean("sleep_weekends", false);
if ((mStatus != SLEEP_HOURS) && ((mStatus != AWAKE) || sleepWeekends)) {
boolean wantSleep = false;
calendar.setTimeInMillis(now);
int day = calendar.get(GregorianCalendar.DAY_OF_WEEK);
if (sleepWeekends && (day == GregorianCalendar.SATURDAY || day == GregorianCalendar.SUNDAY)) {
wantSleep = true;
}
if ((mStatus == AWAKE) && wantSleep) {
mStatus = SLEEP_WEEKEND;
startSleep();
broadcastSleep();
} else if ((mStatus != AWAKE) && !wantSleep) {
// Wake up!!
mStatus = AWAKE;
broadcastSleep();
}
}
boolean offline = mSettings.getBoolean("offline", false);
if (mStatus == AWAKE || mStatus == OFFLINE) {
boolean wantSleep = offline;
if ((mStatus == AWAKE) && wantSleep) {
mStatus = OFFLINE;
startSleep();
broadcastSleep();
} else if ((mStatus == OFFLINE) && !wantSleep) {
// Wake up!!
mStatus = AWAKE;
broadcastSleep();
}
}
if (!mStop && ((mStatus == AWAKE) || mToothpicks)) {
// Start up threads that need it...
try {
Cursor c = getContentResolver().query(EmailContent.Mailbox.CONTENT_URI, EmailContent.Mailbox.CONTENT_PROJECTION, null, null, null);
if (c.moveToFirst()) {
// TODO Could be much faster - just get cursor of ones we're watching...
do {
long mid = c.getLong(EmailContent.Mailbox.CONTENT_ID_COLUMN);
ProtocolService service = serviceMap.get(mid);
if (service == null) {
long freq = c.getInt(EmailContent.Mailbox.CONTENT_SYNC_FREQUENCY_COLUMN);
if (freq == EmailContent.Account.CHECK_INTERVAL_PUSH) {
EmailContent.Mailbox m = EmailContent.getContent(c, EmailContent.Mailbox.class);
// Either we're good to go, or it's been 30 minutes (the default for idle timeout)
if (((m.mFlags & EmailContent.Mailbox.FLAG_CANT_PUSH) == 0) || ((now - m.mSyncTime) > (1000 * 60 * 30L))) {
startService(m);
}
} else if (freq == -19) {
// See if we've got anything to do...
int cnt = EmailContent.count(this, EmailContent.Message.CONTENT_URI, "mailboxKey=" + mid + " and syncServerId=0", null);
if (cnt > 0) {
EmailContent.Mailbox m = EmailContent.getContent(c, EmailContent.Mailbox.class);
startService(new EasOutboxService(this, m), m);
}
} else if (freq > 0 && freq <= 1440) {
long lastSync = c.getLong(EmailContent.Mailbox.CONTENT_SYNC_TIME_COLUMN);
if (now - lastSync > (freq * 60000L)) {
EmailContent.Mailbox m = EmailContent.getContent(c, EmailContent.Mailbox.class);
startService(m);
}
}
} else {
Thread thread = service.mThread;
if (!thread.isAlive()) {
log("Removing dead thread for " + c.getString(EmailContent.Mailbox.CONTENT_DISPLAY_NAME_COLUMN));
serviceMap.remove(mid);
// Restart this if necessary
if (nextWait > 3*SECS) {
nextWait = 3*SECS;
}
} else {
long requestTime = service.mRequestTime;
if (requestTime > 0) {
long timeToRequest = requestTime - now;
if (service instanceof ProtocolService && timeToRequest <= 0) {
service.mRequestTime = 0;
service.ping();
} else if (requestTime > 0 && timeToRequest < nextWait) {
if (timeToRequest < 11*MINS) {
nextWait = timeToRequest < 250 ? 250 : timeToRequest;
} else {
log("Illegal timeToRequest: " + timeToRequest);
}
}
}
}
}
} while (c.moveToNext());
}
c.close();
} catch (Exception e1) {
log("Exception to follow...");
}
}
try {
synchronized (INSTANCE) {
if (nextWait < 0) {
System.err.println("WTF?");
nextWait = 1*SECS;
}
if (nextWait > 30*SECS) {
runAsleep(-1, nextWait - 1000);
}
log("%%MailService sleeping for " + (nextWait / 1000) + " s");
INSTANCE.wait(nextWait);
}
} catch (InterruptedException e) {
log("IOException to follow...");
}
if (mStop) {
startSleep();
log("Shutdown requested.");
return;
}
}
} catch (Throwable e) {
log("MailService crashed.");
} finally {
log("Goodbye.");
}
startService(new Intent(this, SyncManager.class));
throw new RuntimeException("MailService crash; please restart me...");
}
static public void serviceRequest (EmailContent.Mailbox m) {
serviceRequest(m.mId, 10*SECS);
}
static public void serviceRequest (long mailboxId) {
serviceRequest(mailboxId, 10*SECS);
}
static public void serviceRequest (long mailboxId, long ms) {
try {
if (INSTANCE == null)
return;
ProtocolService service = INSTANCE.serviceMap.get(mailboxId);
if (service != null) {
service.mRequestTime = System.currentTimeMillis() + ms;
kick();
} else {
startManualSync(mailboxId);
}
} catch (Exception e) {
e.printStackTrace();
}
}
static public void serviceRequestImmediate (long mailboxId) {
ProtocolService service = INSTANCE.serviceMap.get(mailboxId);
if (service != null) {
service.mRequestTime = System.currentTimeMillis() ;
EmailContent.Mailbox m = EmailContent.Mailbox.restoreMailboxWithId(INSTANCE, mailboxId);
service.mAccount = EmailContent.Account.restoreAccountWithId(INSTANCE, m.mAccountKey);
service.mMailbox = m;
kick();
}
}
static public void partRequest (PartRequest req) {
EmailContent.Message msg = EmailContent.Message.restoreMessageWithId(INSTANCE, req.emailId);
if (msg == null) {
return;
}
long mailboxId = msg.mMailboxKey;
ProtocolService service = INSTANCE.serviceMap.get(mailboxId);
if (service == null)
service = startManualSync(mailboxId);
if (service != null) {
service.mRequestTime = System.currentTimeMillis();
service.addPartRequest(req);
kick();
}
}
static public PartRequest hasPartRequest(long emailId, String part) {
EmailContent.Message msg = EmailContent.Message.restoreMessageWithId(INSTANCE, emailId);
if (msg == null) {
return null;
}
long mailboxId = msg.mMailboxKey;
ProtocolService service = INSTANCE.serviceMap.get(mailboxId);
if (service != null) {
service.mRequestTime = System.currentTimeMillis();
return service.hasPartRequest(emailId, part);
}
return null;
}
static public void cancelPartRequest(long emailId, String part) {
EmailContent.Message msg = EmailContent.Message.restoreMessageWithId(INSTANCE, emailId);
if (msg == null) {
return;
}
long mailboxId = msg.mMailboxKey;
ProtocolService service = INSTANCE.serviceMap.get(mailboxId);
if (service != null) {
service.mRequestTime = System.currentTimeMillis();
service.cancelPartRequest(emailId, part);
}
}
public class SyncStatus {
static public final int NOT_RUNNING = 0;
static public final int DIED = 1;
static public final int SYNC = 2;
static public final int IDLE = 3;
}
static public int getSyncStatus (long mid) {
synchronized (mSyncToken) {
if (INSTANCE == null || INSTANCE.serviceMap == null) {
return SyncStatus.NOT_RUNNING;
}
ProtocolService svc = INSTANCE.serviceMap.get(mid);
if (svc == null) {
return SyncStatus.NOT_RUNNING;
} else {
if (!svc.mThread.isAlive()) {
return SyncStatus.DIED;
} else {
return svc.getSyncStatus();
}
}
}
}
static public ProtocolService startManualSync (long mid) {
if (INSTANCE == null || INSTANCE.serviceMap == null)
return null;
INSTANCE.log("startManualSync");
synchronized (mSyncToken) {
if (INSTANCE.serviceMap.get(mid) == null) {
EmailContent.Mailbox m = EmailContent.Mailbox.restoreMailboxWithId(INSTANCE, mid);
INSTANCE.log("Starting sync for " + m.mDisplayName);
INSTANCE.startService(m);
}
}
return INSTANCE.serviceMap.get(mid);
}
// DO NOT CALL THIS IN A LOOP ON THE SERVICEMAP
static public void stopManualSync (long mid) {
if (INSTANCE == null || INSTANCE.serviceMap == null) {
return;
}
synchronized (mSyncToken) {
ProtocolService svc = INSTANCE.serviceMap.get(mid);
if (svc != null) {
INSTANCE.log("Stopping sync for " + svc.mMailboxName);
svc.stop();
svc.mThread.interrupt();
}
}
}
static public void kick () {
if (INSTANCE == null) {
return;
}
synchronized (INSTANCE) {
INSTANCE.log("We've been kicked!");
INSTANCE.notify();
}
}
static public void kick (long mid) {
EmailContent.Mailbox m = EmailContent.Mailbox.restoreMailboxWithId(INSTANCE, mid);
int syncType = m.mSyncFrequency;
if (syncType == EmailContent.Account.CHECK_INTERVAL_PUSH) {
SyncManager.serviceRequestImmediate(mid);
} else {
SyncManager.startManualSync(mid);
}
}
static public void accountUpdated (long acctId) {
synchronized (mSyncToken) {
for (ProtocolService svc : INSTANCE.serviceMap.values()) {
if (svc.mAccount.mId == acctId) {
svc.mAccount = EmailContent.Account.restoreAccountWithId(INSTANCE, acctId);
}
}
}
}
static public int status () {
return mStatus;
}
static public boolean isSleeping () {
return (mStatus == SLEEP_HOURS || mStatus == SLEEP_WEEKEND);
}
static public void forceAwake (boolean wake) {
mToothpicks = wake;
kick();
}
static public boolean isForceAwake () {
return mToothpicks;
}
static public void done (ProtocolService svc) {
INSTANCE.serviceMap.remove(svc.mMailboxId);
}
public static void shutdown () {
INSTANCE.mStop = true;
kick();
INSTANCE.stopSelf();
}
static public String serviceName (long id) {
if (id < 0) {
return "MailService";
} else {
ProtocolService service = INSTANCE.serviceMap.get(id);
if (service != null) {
return service.mThread.getName();
} else {
return "Not running?";
}
}
}
static public Context getContext () {
if (INSTANCE == null) {
return null;
}
return (Context)INSTANCE;
}
}

View File

@ -0,0 +1,46 @@
/* Copyright (c) 2002,2003, Stefan Haustein, Oberhausen, Rhld., Germany
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE. */
package com.android.exchange;
/** contains the WBXML constants */
public interface Wbxml {
static public final int SWITCH_PAGE = 0;
static public final int END = 1;
static public final int ENTITY = 2;
static public final int STR_I = 3;
static public final int LITERAL = 4;
static public final int EXT_I_0 = 0x40;
static public final int EXT_I_1 = 0x41;
static public final int EXT_I_2 = 0x42;
static public final int PI = 0x43;
static public final int LITERAL_C = 0x44;
static public final int EXT_T_0 = 0x80;
static public final int EXT_T_1 = 0x81;
static public final int EXT_T_2 = 0x82;
static public final int STR_T = 0x83;
static public final int LITERAL_A = 0x084;
static public final int EXT_0 = 0x0c0;
static public final int EXT_1 = 0x0c1;
static public final int EXT_2 = 0x0c2;
static public final int OPAQUE = 0x0c3;
static public final int LITERAL_AC = 0x0c4;
}

View File

@ -0,0 +1,265 @@
/* Copyright (c) 2002,2003, Stefan Haustein, Oberhausen, Rhld., Germany
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE. */
//Contributors: Jonathan Cox, Bogdan Onoiu, Jerry Tian
//Simplified for Google, Inc. by Marc Blank
package com.android.exchange;
import java.io.*;
import java.util.*;
import org.xmlpull.v1.*;
/**
* A class for writing WBXML.
*
*/
public class WbxmlSerializer implements XmlSerializer {
Hashtable<String, Integer> stringTable = new Hashtable<String, Integer>();
OutputStream out;
ByteArrayOutputStream buf = new ByteArrayOutputStream();
ByteArrayOutputStream stringTableBuf = new ByteArrayOutputStream();
String pending;
int depth;
String name;
Hashtable<String, Object> tagTable = new Hashtable<String, Object>();
private int tagPage;
public void cdsect (String cdsect) throws IOException{
text (cdsect);
}
public void comment (String comment) {
}
public void docdecl (String docdecl) {
throw new RuntimeException ("Cannot write docdecl for WBXML");
}
public void entityRef (String er) {
throw new RuntimeException ("EntityReference not supported for WBXML");
}
public int getDepth() {
return depth;
}
public boolean getFeature (String name) {
return false;
}
public String getNamespace() {
throw new RuntimeException("NYI");
}
public String getName() {
throw new RuntimeException("NYI");
}
public String getPrefix(String nsp, boolean create) {
throw new RuntimeException ("NYI");
}
public Object getProperty (String name) {
return null;
}
public void ignorableWhitespace (String sp) {
}
public void endDocument() throws IOException {
writeInt(out, stringTableBuf.size());
out.write(stringTableBuf.toByteArray());
out.write(buf.toByteArray());
out.flush();
}
/** ATTENTION: flush cannot work since Wbxml documents require
buffering. Thus, this call does nothing. */
public void flush() {
}
public void checkPending(boolean degenerated) throws IOException {
if (pending == null)
return;
int[] idx = (int[]) tagTable.get(pending);
// if no entry in known table, then add as literal
if (idx == null) {
throw new IOException("Bad tag: " + pending);
}
else {
if(idx[0] != tagPage) {
tagPage=idx[0];
buf.write(Wbxml.SWITCH_PAGE);
buf.write(tagPage);
}
buf.write(degenerated ? idx[1] : idx[1] | 64);
}
pending = null;
}
public void processingInstruction(String pi) {
}
public void setFeature(String name, boolean value) {
}
public void setOutput (Writer writer) {
}
public void setOutput (OutputStream out, String encoding) throws IOException {
this.out = out;
buf = new ByteArrayOutputStream();
stringTableBuf = new ByteArrayOutputStream();
}
public OutputStream getOutput () {
return out;
}
public void setPrefix(String prefix, String nsp) {
throw new RuntimeException("NYI");
}
public void setProperty(String property, Object value) {
throw new IllegalArgumentException ("unknown property "+property);
}
public void startDocument(String s, Boolean b) throws IOException{
out.write(0x03); // version 1.3
out.write(0x01); // unknown or missing public identifier
out.write(106);
}
public XmlSerializer startTag(String namespace, String name) throws IOException {
checkPending(false);
pending = name;
depth++;
return this;
}
public XmlSerializer text(char[] chars, int start, int len) throws IOException {
checkPending(false);
buf.write(Wbxml.STR_I);
writeStrI(buf, new String(chars, start, len));
return this;
}
public XmlSerializer text(String text) throws IOException {
checkPending(false);
buf.write(Wbxml.STR_I);
writeStrI(buf, text);
return this;
}
/** Used in text() and attribute() to write text */
public XmlSerializer endTag(String namespace, String name) throws IOException {
if (pending != null) {
checkPending(true);
} else {
buf.write(Wbxml.END);
}
depth--;
return this;
}
// ------------- internal methods --------------------------
static void writeInt(OutputStream out, int i) throws IOException {
byte[] buf = new byte[5];
int idx = 0;
do {
buf[idx++] = (byte) (i & 0x7f);
i = i >> 7;
} while (i != 0);
while (idx > 1) {
out.write(buf[--idx] | 0x80);
}
out.write(buf[0]);
}
void writeStrI(OutputStream out, String s) throws IOException {
byte[] data = s.getBytes("UTF-8");
out.write(data);
out.write(0);
}
public Hashtable<String, Object> getTagTable () {
return this.tagTable;
}
public void setTagTable (Hashtable<String, Object> tagTable) {
this.tagTable = tagTable;
}
/**
* Sets the tag table for a given page.
* The first string in the array defines tag 5, the second tag 6 etc.
*/
public void setTagTable(int page, String[] tagTable) {
for (int i = 0; i < tagTable.length; i++) {
if (tagTable[i] != null) {
Object idx = new int[]{page, i+5};
this.tagTable.put(tagTable[i], idx);
}
}
}
public XmlSerializer attribute(String namespace, String name, String value)
throws IOException, IllegalArgumentException, IllegalStateException {
return null;
}
}