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:
parent
553603337a
commit
2c67f1f8b8
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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...");
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue