1
0
mirror of https://github.com/moparisthebest/k-9 synced 2025-01-10 13:18:09 -05:00

Update EasStore for the new URI scheme. Implement the DeviceID for EAS.

This commit is contained in:
wongk 2011-12-30 14:51:48 -05:00
parent cdba29b002
commit 41b292ee9d
3 changed files with 262 additions and 171 deletions

View File

@ -5,6 +5,7 @@ import java.io.File;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue; import java.util.concurrent.SynchronousQueue;
@ -14,6 +15,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException;
@ -21,6 +23,7 @@ import android.net.Uri;
import android.os.Environment; import android.os.Environment;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.text.TextUtils;
import android.text.format.Time; import android.text.format.Time;
import android.util.Log; import android.util.Log;
@ -54,7 +57,7 @@ public class K9 extends Application {
void initializeComponent(K9 application); void initializeComponent(K9 application);
} }
public static Application app = null; public static K9 app = null;
public static File tempDirectory; public static File tempDirectory;
public static final String LOG_TAG = "k9"; public static final String LOG_TAG = "k9";
@ -76,6 +79,7 @@ public class K9 extends Application {
private static final FontSizes fontSizes = new FontSizes(); private static final FontSizes fontSizes = new FontSizes();
private static BACKGROUND_OPS backgroundOps = BACKGROUND_OPS.WHEN_CHECKED; private static BACKGROUND_OPS backgroundOps = BACKGROUND_OPS.WHEN_CHECKED;
/** /**
* Some log messages can be sent to a file, so that the logs * Some log messages can be sent to a file, so that the logs
* can be read using unprivileged access (eg. Terminal Emulator) * can be read using unprivileged access (eg. Terminal Emulator)
@ -91,7 +95,6 @@ public class K9 extends Application {
**/ **/
public static boolean DEVELOPER_MODE = true; public static boolean DEVELOPER_MODE = true;
/** /**
* If this is enabled there will be additional logging information sent to * If this is enabled there will be additional logging information sent to
* Log.d, including protocol dumps. * Log.d, including protocol dumps.
@ -103,33 +106,26 @@ public class K9 extends Application {
* Should K-9 log the conversation it has over the wire with * Should K-9 log the conversation it has over the wire with
* SMTP servers? * SMTP servers?
*/ */
public static boolean DEBUG_PROTOCOL_SMTP = true; public static boolean DEBUG_PROTOCOL_SMTP = true;
/** /**
* Should K-9 log the conversation it has over the wire with * Should K-9 log the conversation it has over the wire with
* IMAP servers? * IMAP servers?
*/ */
public static boolean DEBUG_PROTOCOL_IMAP = true; public static boolean DEBUG_PROTOCOL_IMAP = true;
/** /**
* Should K-9 log the conversation it has over the wire with * Should K-9 log the conversation it has over the wire with
* POP3 servers? * POP3 servers?
*/ */
public static boolean DEBUG_PROTOCOL_POP3 = true; public static boolean DEBUG_PROTOCOL_POP3 = true;
/** /**
* Should K-9 log the conversation it has over the wire with * Should K-9 log the conversation it has over the wire with
* WebDAV servers? * WebDAV servers?
*/ */
public static boolean DEBUG_PROTOCOL_WEBDAV = true; public static boolean DEBUG_PROTOCOL_WEBDAV = true;
/** /**
* If this is enabled than logging that normally hides sensitive information * If this is enabled than logging that normally hides sensitive information
* like passwords will show that information. * like passwords will show that information.
@ -144,18 +140,15 @@ public class K9 extends Application {
public static String ERROR_FOLDER_NAME = "K9mail-errors"; public static String ERROR_FOLDER_NAME = "K9mail-errors";
private static boolean mAnimations = true; private static boolean mAnimations = true;
private static boolean mConfirmDelete = false; private static boolean mConfirmDelete = false;
private static boolean mConfirmDeleteStarred = false; private static boolean mConfirmDeleteStarred = false;
private static boolean mConfirmSpam = false; private static boolean mConfirmSpam = false;
private static boolean mConfirmMarkAllAsRead = true; private static boolean mConfirmMarkAllAsRead = true;
private static boolean mKeyguardPrivacy = false; private static boolean mKeyguardPrivacy = false;
private static boolean mMessageListStars = true; private static boolean mMessageListStars = true;
private static boolean mMessageListCheckboxes = false; private static boolean mMessageListCheckboxes = false;
private static boolean mMessageListTouchable = false; private static boolean mMessageListTouchable = false;
private static int mMessageListPreviewLines = 2; private static int mMessageListPreviewLines = 2;
private static boolean mShowCorrespondentNames = true; private static boolean mShowCorrespondentNames = true;
private static boolean mShowContactName = false; private static boolean mShowContactName = false;
private static boolean mChangeContactNameColor = false; private static boolean mChangeContactNameColor = false;
@ -163,7 +156,6 @@ public class K9 extends Application {
private static boolean mMessageViewFixedWidthFont = false; private static boolean mMessageViewFixedWidthFont = false;
private static boolean mMessageViewReturnToList = false; private static boolean mMessageViewReturnToList = false;
private static boolean mMessageViewShowNext = false; private static boolean mMessageViewShowNext = false;
private static boolean mGesturesEnabled = true; private static boolean mGesturesEnabled = true;
private static boolean mUseVolumeKeysForNavigation = false; private static boolean mUseVolumeKeysForNavigation = false;
private static boolean mUseVolumeKeysForListNavigation = false; private static boolean mUseVolumeKeysForListNavigation = false;
@ -179,11 +171,9 @@ public class K9 extends Application {
private static String mQuietTimeEnds = null; private static String mQuietTimeEnds = null;
private static boolean compactLayouts = false; private static boolean compactLayouts = false;
private static String mAttachmentDefaultPath = ""; private static String mAttachmentDefaultPath = "";
private static boolean useGalleryBugWorkaround = false; private static boolean useGalleryBugWorkaround = false;
private static boolean galleryBuggy; private static boolean galleryBuggy;
private static String mDeviceId = null;
/** /**
* The MIME type(s) of attachments we're willing to view. * The MIME type(s) of attachments we're willing to view.
@ -238,10 +228,10 @@ public class K9 extends Application {
public static final int MAX_ATTACHMENT_DOWNLOAD_SIZE = (128 * 1024 * 1024); public static final int MAX_ATTACHMENT_DOWNLOAD_SIZE = (128 * 1024 * 1024);
/* How many times should K-9 try to deliver a message before giving up /**
* How many times should K-9 try to deliver a message before giving up
* until the app is killed and restarted * until the app is killed and restarted
*/ */
public static int MAX_SEND_ATTEMPTS = 5; public static int MAX_SEND_ATTEMPTS = 5;
/** /**
@ -262,17 +252,11 @@ public class K9 extends Application {
*/ */
public static final int NOTIFICATION_LED_ON_TIME = 500; public static final int NOTIFICATION_LED_ON_TIME = 500;
public static final int NOTIFICATION_LED_OFF_TIME = 2000; public static final int NOTIFICATION_LED_OFF_TIME = 2000;
public static final boolean NOTIFICATION_LED_WHILE_SYNCING = false; public static final boolean NOTIFICATION_LED_WHILE_SYNCING = false;
public static final int NOTIFICATION_LED_FAST_ON_TIME = 100; public static final int NOTIFICATION_LED_FAST_ON_TIME = 100;
public static final int NOTIFICATION_LED_FAST_OFF_TIME = 100; public static final int NOTIFICATION_LED_FAST_OFF_TIME = 100;
public static final int NOTIFICATION_LED_BLINK_SLOW = 0; public static final int NOTIFICATION_LED_BLINK_SLOW = 0;
public static final int NOTIFICATION_LED_BLINK_FAST = 1; public static final int NOTIFICATION_LED_BLINK_FAST = 1;
public static final int NOTIFICATION_LED_SENDING_FAILURE_COLOR = 0xffff0000; public static final int NOTIFICATION_LED_SENDING_FAILURE_COLOR = 0xffff0000;
// Must not conflict with an account number // Must not conflict with an account number
@ -280,7 +264,6 @@ public class K9 extends Application {
public static final int SEND_FAILED_NOTIFICATION = -1500; public static final int SEND_FAILED_NOTIFICATION = -1500;
public static final int CONNECTIVITY_ID = -3; public static final int CONNECTIVITY_ID = -3;
public static class Intents { public static class Intents {
public static class EmailReceived { public static class EmailReceived {
@ -317,7 +300,6 @@ public class K9 extends Application {
int acctLength = Preferences.getPreferences(context).getAvailableAccounts().size(); int acctLength = Preferences.getPreferences(context).getAvailableAccounts().size();
setServicesEnabled(context, acctLength > 0, null); setServicesEnabled(context, acctLength > 0, null);
} }
private static void setServicesEnabled(Context context, boolean enabled, Integer wakeLockId) { private static void setServicesEnabled(Context context, boolean enabled, Integer wakeLockId) {
@ -356,7 +338,6 @@ public class K9 extends Application {
*/ */
MailService.actionReset(context, wakeLockId); MailService.actionReset(context, wakeLockId);
} }
} }
/** /**
@ -414,7 +395,6 @@ public class K9 extends Application {
editor.putBoolean("quietTimeEnabled", mQuietTimeEnabled); editor.putBoolean("quietTimeEnabled", mQuietTimeEnabled);
editor.putString("quietTimeStarts", mQuietTimeStarts); editor.putString("quietTimeStarts", mQuietTimeStarts);
editor.putString("quietTimeEnds", mQuietTimeEnds); editor.putString("quietTimeEnds", mQuietTimeEnds);
editor.putBoolean("startIntegratedInbox", mStartIntegratedInbox); editor.putBoolean("startIntegratedInbox", mStartIntegratedInbox);
editor.putBoolean("measureAccounts", mMeasureAccounts); editor.putBoolean("measureAccounts", mMeasureAccounts);
editor.putBoolean("countSearchMessages", mCountSearchMessages); editor.putBoolean("countSearchMessages", mCountSearchMessages);
@ -423,7 +403,6 @@ public class K9 extends Application {
editor.putBoolean("messageListCheckboxes", mMessageListCheckboxes); editor.putBoolean("messageListCheckboxes", mMessageListCheckboxes);
editor.putBoolean("messageListTouchable", mMessageListTouchable); editor.putBoolean("messageListTouchable", mMessageListTouchable);
editor.putInt("messageListPreviewLines", mMessageListPreviewLines); editor.putInt("messageListPreviewLines", mMessageListPreviewLines);
editor.putBoolean("showCorrespondentNames", mShowCorrespondentNames); editor.putBoolean("showCorrespondentNames", mShowCorrespondentNames);
editor.putBoolean("showContactName", mShowContactName); editor.putBoolean("showContactName", mShowContactName);
editor.putBoolean("changeRegisteredNameColor", mChangeContactNameColor); editor.putBoolean("changeRegisteredNameColor", mChangeContactNameColor);
@ -431,20 +410,17 @@ public class K9 extends Application {
editor.putBoolean("messageViewFixedWidthFont", mMessageViewFixedWidthFont); editor.putBoolean("messageViewFixedWidthFont", mMessageViewFixedWidthFont);
editor.putBoolean("messageViewReturnToList", mMessageViewReturnToList); editor.putBoolean("messageViewReturnToList", mMessageViewReturnToList);
editor.putBoolean("messageViewShowNext", mMessageViewShowNext); editor.putBoolean("messageViewShowNext", mMessageViewShowNext);
editor.putString("language", language); editor.putString("language", language);
editor.putInt("theme", theme); editor.putInt("theme", theme);
editor.putBoolean("useGalleryBugWorkaround", useGalleryBugWorkaround); editor.putBoolean("useGalleryBugWorkaround", useGalleryBugWorkaround);
editor.putBoolean("confirmDelete", mConfirmDelete); editor.putBoolean("confirmDelete", mConfirmDelete);
editor.putBoolean("confirmDeleteStarred", mConfirmDeleteStarred); editor.putBoolean("confirmDeleteStarred", mConfirmDeleteStarred);
editor.putBoolean("confirmSpam", mConfirmSpam); editor.putBoolean("confirmSpam", mConfirmSpam);
editor.putBoolean("confirmMarkAllAsRead", mConfirmMarkAllAsRead); editor.putBoolean("confirmMarkAllAsRead", mConfirmMarkAllAsRead);
editor.putBoolean("keyguardPrivacy", mKeyguardPrivacy); editor.putBoolean("keyguardPrivacy", mKeyguardPrivacy);
editor.putBoolean("compactLayouts", compactLayouts); editor.putBoolean("compactLayouts", compactLayouts);
editor.putString("attachmentdefaultpath", mAttachmentDefaultPath); editor.putString("attachmentdefaultpath", mAttachmentDefaultPath);
editor.putString("deviceId", mDeviceId);
fontSizes.save(editor); fontSizes.save(editor);
} }
@ -454,7 +430,6 @@ public class K9 extends Application {
super.onCreate(); super.onCreate();
app = this; app = this;
galleryBuggy = checkForBuggyGallery(); galleryBuggy = checkForBuggyGallery();
Preferences prefs = Preferences.getPreferences(this); Preferences prefs = Preferences.getPreferences(this);
@ -469,7 +444,6 @@ public class K9 extends Application {
/* /*
* Enable background sync of messages * Enable background sync of messages
*/ */
setServicesEnabled(this); setServicesEnabled(this);
registerReceivers(); registerReceivers();
@ -547,14 +521,11 @@ public class K9 extends Application {
mMessageListCheckboxes = sprefs.getBoolean("messageListCheckboxes", false); mMessageListCheckboxes = sprefs.getBoolean("messageListCheckboxes", false);
mMessageListTouchable = sprefs.getBoolean("messageListTouchable", false); mMessageListTouchable = sprefs.getBoolean("messageListTouchable", false);
mMessageListPreviewLines = sprefs.getInt("messageListPreviewLines", 2); mMessageListPreviewLines = sprefs.getInt("messageListPreviewLines", 2);
mMobileOptimizedLayout = sprefs.getBoolean("mobileOptimizedLayout", false); mMobileOptimizedLayout = sprefs.getBoolean("mobileOptimizedLayout", false);
mZoomControlsEnabled = sprefs.getBoolean("zoomControlsEnabled", false); mZoomControlsEnabled = sprefs.getBoolean("zoomControlsEnabled", false);
mQuietTimeEnabled = sprefs.getBoolean("quietTimeEnabled", false); mQuietTimeEnabled = sprefs.getBoolean("quietTimeEnabled", false);
mQuietTimeStarts = sprefs.getString("quietTimeStarts", "21:00"); mQuietTimeStarts = sprefs.getString("quietTimeStarts", "21:00");
mQuietTimeEnds = sprefs.getString("quietTimeEnds", "7:00"); mQuietTimeEnds = sprefs.getString("quietTimeEnds", "7:00");
mShowCorrespondentNames = sprefs.getBoolean("showCorrespondentNames", true); mShowCorrespondentNames = sprefs.getBoolean("showCorrespondentNames", true);
mShowContactName = sprefs.getBoolean("showContactName", false); mShowContactName = sprefs.getBoolean("showContactName", false);
mChangeContactNameColor = sprefs.getBoolean("changeRegisteredNameColor", false); mChangeContactNameColor = sprefs.getBoolean("changeRegisteredNameColor", false);
@ -562,19 +533,15 @@ public class K9 extends Application {
mMessageViewFixedWidthFont = sprefs.getBoolean("messageViewFixedWidthFont", false); mMessageViewFixedWidthFont = sprefs.getBoolean("messageViewFixedWidthFont", false);
mMessageViewReturnToList = sprefs.getBoolean("messageViewReturnToList", false); mMessageViewReturnToList = sprefs.getBoolean("messageViewReturnToList", false);
mMessageViewShowNext = sprefs.getBoolean("messageViewShowNext", false); mMessageViewShowNext = sprefs.getBoolean("messageViewShowNext", false);
useGalleryBugWorkaround = sprefs.getBoolean("useGalleryBugWorkaround", K9.isGalleryBuggy()); useGalleryBugWorkaround = sprefs.getBoolean("useGalleryBugWorkaround", K9.isGalleryBuggy());
mConfirmDelete = sprefs.getBoolean("confirmDelete", false); mConfirmDelete = sprefs.getBoolean("confirmDelete", false);
mConfirmDeleteStarred = sprefs.getBoolean("confirmDeleteStarred", false); mConfirmDeleteStarred = sprefs.getBoolean("confirmDeleteStarred", false);
mConfirmSpam = sprefs.getBoolean("confirmSpam", false); mConfirmSpam = sprefs.getBoolean("confirmSpam", false);
mConfirmMarkAllAsRead = sprefs.getBoolean("confirmMarkAllAsRead", true); mConfirmMarkAllAsRead = sprefs.getBoolean("confirmMarkAllAsRead", true);
mKeyguardPrivacy = sprefs.getBoolean("keyguardPrivacy", false); mKeyguardPrivacy = sprefs.getBoolean("keyguardPrivacy", false);
compactLayouts = sprefs.getBoolean("compactLayouts", false); compactLayouts = sprefs.getBoolean("compactLayouts", false);
mAttachmentDefaultPath = sprefs.getString("attachmentdefaultpath", Environment.getExternalStorageDirectory().toString()); mAttachmentDefaultPath = sprefs.getString("attachmentdefaultpath", Environment.getExternalStorageDirectory().toString());
mDeviceId = sprefs.getString("deviceId", null);
fontSizes.load(sprefs); fontSizes.load(sprefs);
try { try {
@ -601,10 +568,8 @@ public class K9 extends Application {
// Discard , as it means we're not running on a device with strict mode // Discard , as it means we're not running on a device with strict mode
Log.v(K9.LOG_TAG, "Failed to turn on strict mode", e); Log.v(K9.LOG_TAG, "Failed to turn on strict mode", e);
} }
} }
/** /**
* since Android invokes Application.onCreate() only after invoking all * since Android invokes Application.onCreate() only after invoking all
* other components' onCreate(), here is a way to notify interested * other components' onCreate(), here is a way to notify interested
@ -738,7 +703,6 @@ public class K9 extends Application {
mQuietTimeEnds = quietTimeEnds; mQuietTimeEnds = quietTimeEnds;
} }
public static boolean isQuietTime() { public static boolean isQuietTime() {
if (!mQuietTimeEnabled) { if (!mQuietTimeEnabled) {
return false; return false;
@ -760,7 +724,6 @@ public class K9 extends Application {
return false; return false;
} }
// 21:00 - 05:00 means we want to be quiet if it's after 9 or before 5 // 21:00 - 05:00 means we want to be quiet if it's after 9 or before 5
if (quietStarts > quietEnds) { if (quietStarts > quietEnds) {
// if it's 22:00 or 03:00 but not 8:00 // if it's 22:00 or 03:00 but not 8:00
@ -781,8 +744,6 @@ public class K9 extends Application {
return false; return false;
} }
public static boolean startIntegratedInbox() { public static boolean startIntegratedInbox() {
return mStartIntegratedInbox; return mStartIntegratedInbox;
} }
@ -1016,4 +977,26 @@ public class K9 extends Application {
public static void setAttachmentDefaultPath(String attachmentDefaultPath) { public static void setAttachmentDefaultPath(String attachmentDefaultPath) {
K9.mAttachmentDefaultPath = attachmentDefaultPath; K9.mAttachmentDefaultPath = attachmentDefaultPath;
} }
/**
* Generates a uuid that is to identify this application on this device.
* The uuid is only generated once, and then persisted.
* @return the device Id
*/
public String getDeviceId() {
if (TextUtils.isEmpty(mDeviceId)) {
// Remove the dashes from the UUID, so that we have alphanumeric characters only.
// This increases the likelyhood of compatability with external systems that may
// use the ID.
mDeviceId = UUID.randomUUID().toString().replace("-", "");
// This particular setting is not editted through the settings view, but instead only
// set in this method. We need to commit it now to ensure it is persisted correctly.
final Preferences preferences = Preferences.getPreferences(this);
Editor editor = preferences.getPreferences().edit();
editor.putString("deviceId", mDeviceId);
editor.commit();
}
return mDeviceId;
}
} }

View File

@ -99,6 +99,7 @@ public abstract class Store {
* @see ImapStore#decodeUri(String) * @see ImapStore#decodeUri(String)
* @see Pop3Store#decodeUri(String) * @see Pop3Store#decodeUri(String)
* @see WebDavStore#decodeUri(String) * @see WebDavStore#decodeUri(String)
* @see EasStore#decodeUri(String)
*/ */
public static ServerSettings decodeStoreUri(String uri) { public static ServerSettings decodeStoreUri(String uri) {
if (uri.startsWith("imap")) { if (uri.startsWith("imap")) {
@ -107,6 +108,8 @@ public abstract class Store {
return Pop3Store.decodeUri(uri); return Pop3Store.decodeUri(uri);
} else if (uri.startsWith("webdav")) { } else if (uri.startsWith("webdav")) {
return WebDavStore.decodeUri(uri); return WebDavStore.decodeUri(uri);
} else if (uri.startsWith("eas")) {
return EasStore.decodeUri(uri);
} else { } else {
throw new IllegalArgumentException("Not a valid store URI"); throw new IllegalArgumentException("Not a valid store URI");
} }
@ -123,6 +126,7 @@ public abstract class Store {
* @see ImapStore#createUri(ServerSettings) * @see ImapStore#createUri(ServerSettings)
* @see Pop3Store#createUri(ServerSettings) * @see Pop3Store#createUri(ServerSettings)
* @see WebDavStore#createUri(ServerSettings) * @see WebDavStore#createUri(ServerSettings)
* @see EasStore#createUri(ServerSettings)
*/ */
public static String createStoreUri(ServerSettings server) { public static String createStoreUri(ServerSettings server) {
if (ImapStore.STORE_TYPE.equals(server.type)) { if (ImapStore.STORE_TYPE.equals(server.type)) {
@ -131,6 +135,8 @@ public abstract class Store {
return Pop3Store.createUri(server); return Pop3Store.createUri(server);
} else if (WebDavStore.STORE_TYPE.equals(server.type)) { } else if (WebDavStore.STORE_TYPE.equals(server.type)) {
return WebDavStore.createUri(server); return WebDavStore.createUri(server);
} else if (EasStore.STORE_TYPE.equals(server.type)) {
return EasStore.createUri(server);
} else { } else {
throw new IllegalArgumentException("Not a valid store URI"); throw new IllegalArgumentException("Not a valid store URI");
} }

View File

@ -39,7 +39,6 @@ import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams; import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams; import org.apache.http.params.HttpParams;
import android.content.Context;
import android.os.PowerManager; import android.os.PowerManager;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Base64; import android.util.Base64;
@ -51,6 +50,7 @@ import com.fsck.k9.controller.MessageRetrievalListener;
import com.fsck.k9.helper.power.TracingPowerManager; import com.fsck.k9.helper.power.TracingPowerManager;
import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock; import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock;
import com.fsck.k9.mail.AuthenticationFailedException; import com.fsck.k9.mail.AuthenticationFailedException;
import com.fsck.k9.mail.ConnectionSecurity;
import com.fsck.k9.mail.FetchProfile; import com.fsck.k9.mail.FetchProfile;
import com.fsck.k9.mail.Flag; import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder; import com.fsck.k9.mail.Folder;
@ -58,6 +58,7 @@ import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.PushReceiver; import com.fsck.k9.mail.PushReceiver;
import com.fsck.k9.mail.Pusher; import com.fsck.k9.mail.Pusher;
import com.fsck.k9.mail.ServerSettings;
import com.fsck.k9.mail.Store; import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.filter.EOLConvertingOutputStream; import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
import com.fsck.k9.mail.internet.MimeMessage; import com.fsck.k9.mail.internet.MimeMessage;
@ -89,31 +90,32 @@ public class EasStore extends Store {
// we sync the items any "collection" (emails in a folder). // we sync the items any "collection" (emails in a folder).
private static final String INITIAL_SYNC_KEY = "0"; private static final String INITIAL_SYNC_KEY = "0";
static private final String PING_COMMAND = "Ping"; private static final String PING_COMMAND = "Ping";
static private final String PROVISION_COMMAND = "Provision"; private static final String PROVISION_COMMAND = "Provision";
// Command timeout is the the time allowed for reading data from an open connection before an // Command timeout is the the time allowed for reading data from an open connection before an
// IOException is thrown. After a small added allowance, our watch dog alarm goes off (allowing // IOException is thrown. After a small added allowance, our watch dog alarm goes off (allowing
// us to detect a silently dropped connection). The allowance is defined below. // us to detect a silently dropped connection). The allowance is defined below.
static private final int COMMAND_TIMEOUT = 30 * 1000; private static final int COMMAND_TIMEOUT = 30 * 1000;
// Connection timeout is the time given to connect to the server before reporting an IOException. // Connection timeout is the time given to connect to the server before reporting an IOException.
static private final int CONNECTION_TIMEOUT = 20 * 1000; private static final int CONNECTION_TIMEOUT = 20 * 1000;
// This needs to be long enough to send the longest reasonable message, without being so long // This needs to be long enough to send the longest reasonable message, without being so long
// as to effectively "hang" sending of mail. The standard 30 second timeout isn't long enough // as to effectively "hang" sending of mail. The standard 30 second timeout isn't long enough
// for pictures and the like. For now, we'll use 15 minutes, in the knowledge that any socket // for pictures and the like. For now, we'll use 15 minutes, in the knowledge that any socket
// failure would probably generate an Exception before timing out anyway. // failure would probably generate an Exception before timing out anyway.
public static final int SEND_MAIL_TIMEOUT = 15 * 60 * 1000; private static final int SEND_MAIL_TIMEOUT = 15 * 60 * 1000;
// MSFT's custom HTTP result code indicating the need to provision. // MSFT's custom HTTP result code indicating the need to provision.
static private final int HTTP_NEED_PROVISIONING = 449; private static final int HTTP_NEED_PROVISIONING = 449;
// The EAS protocol Provision status for "we implement all of the policies". // The EAS protocol Provision status for "we implement all of the policies".
static private final String PROVISION_STATUS_OK = "1"; private static final String PROVISION_STATUS_OK = "1";
// The EAS protocol Provision status meaning "we partially implement the policies". // The EAS protocol Provision status meaning "we partially implement the policies".
static private final String PROVISION_STATUS_PARTIAL = "2"; private static final String PROVISION_STATUS_PARTIAL = "2";
static public final String EAS_12_POLICY_TYPE = "MS-EAS-Provisioning-WBXML"; public static final String EAS_12_POLICY_TYPE = "MS-EAS-Provisioning-WBXML";
static public final String EAS_2_POLICY_TYPE = "MS-WAP-Provisioning-XML"; public static final String EAS_2_POLICY_TYPE = "MS-WAP-Provisioning-XML";
private static final int IDLE_READ_TIMEOUT_INCREMENT = 5 * 60 * 1000; private static final int IDLE_READ_TIMEOUT_INCREMENT = 5 * 60 * 1000;
private static final int IDLE_FAILURE_COUNT_LIMIT = 10; private static final int IDLE_FAILURE_COUNT_LIMIT = 10;
@ -122,77 +124,178 @@ public class EasStore extends Store {
// The number of emails to fetch for each request to the server. // The number of emails to fetch for each request to the server.
private static final int EMAIL_WINDOW_SIZE = 10; private static final int EMAIL_WINDOW_SIZE = 10;
// The maximum length of the DeviceID parameter used by EAS is 32 characters.
private static final int MAX_DEVICE_ID_SIZE = 32;
public String mProtocolVersion; /**
public Double mProtocolVersionDouble; * Decodes a EasStore URI.
protected String mDeviceId = null; *
protected String mDeviceType = "Android"; * <p>Possible forms:</p>
private String mCmdString = null; * <pre>
* eas://user:password@server:port CONNECTION_SECURITY_NONE
* eas+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL
* eas+tls+://user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED
* eas+ssl+://user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED
* eas+ssl://user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL
* </pre>
*/
public static ServerSettings decodeUri(String uri) {
String host;
int port;
ConnectionSecurity connectionSecurity;
String username = null;
String password = null;
private final URI mUri; /* Stores the Uniform Resource Indicator with all connection info */ URI easUri;
private String mHost; /* Stores the host name for the server */ try {
private String mUsername; /* Stores the username for authentications */ easUri = new URI(uri);
private String mPassword; /* Stores the password for authentications */ } catch (URISyntaxException use) {
throw new IllegalArgumentException("Invalid EasStore URI", use);
}
String scheme = easUri.getScheme();
if (scheme.equals("eas")) {
connectionSecurity = ConnectionSecurity.NONE;
} else if (scheme.equals("eas+ssl")) {
connectionSecurity = ConnectionSecurity.SSL_TLS_OPTIONAL;
} else if (scheme.equals("eas+ssl+")) {
connectionSecurity = ConnectionSecurity.SSL_TLS_REQUIRED;
} else if (scheme.equals("eas+tls")) {
connectionSecurity = ConnectionSecurity.STARTTLS_OPTIONAL;
} else if (scheme.equals("eas+tls+")) {
connectionSecurity = ConnectionSecurity.STARTTLS_REQUIRED;
} else {
throw new IllegalArgumentException("Unsupported protocol (" + scheme + ")");
}
host = easUri.getHost();
if (host.startsWith("http")) {
String[] hostParts = host.split("://", 2);
if (hostParts.length > 1) {
host = hostParts[1];
}
}
port = easUri.getPort();
if (easUri.getUserInfo() != null) {
try {
String[] userInfoParts = easUri.getUserInfo().split(":");
username = URLDecoder.decode(userInfoParts[0], "UTF-8");
if (userInfoParts.length > 1) {
password = URLDecoder.decode(userInfoParts[1], "UTF-8");
}
} catch (UnsupportedEncodingException enc) {
// This shouldn't happen since the encoding is hardcoded to UTF-8
throw new IllegalArgumentException("Couldn't urldecode username or password.", enc);
}
}
return new ServerSettings(STORE_TYPE, host, port, connectionSecurity, null, username, password);
}
/**
* Creates a EasStore URI with the supplied settings.
*
* @param server
* The {@link ServerSettings} object that holds the server settings.
*
* @return A EasStore URI that holds the same information as the {@code server} parameter.
*
* @see Account#getStoreUri()
* @see EasStore#decodeUri(String)
*/
public static String createUri(ServerSettings server) {
String userEnc;
String passwordEnc;
try {
userEnc = URLEncoder.encode(server.username, "UTF-8");
passwordEnc = (server.password != null) ?
URLEncoder.encode(server.password, "UTF-8") : "";
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException("Could not encode username or password", e);
}
String userInfo = userEnc + ":" + passwordEnc;
String scheme;
switch (server.connectionSecurity) {
case SSL_TLS_OPTIONAL:
scheme = "eas+ssl";
break;
case SSL_TLS_REQUIRED:
scheme = "eas+ssl+";
break;
case STARTTLS_OPTIONAL:
scheme = "eas+tls";
break;
case STARTTLS_REQUIRED:
scheme = "eas+tls+";
break;
default:
case NONE:
scheme = "eas";
break;
}
try {
return new URI(scheme, userInfo, server.host, server.port, null,
null, null).toString();
} catch (URISyntaxException e) {
throw new IllegalArgumentException("Can't create EasStore URI", e);
}
}
// The following members are set during the first contact with the Exchange server, before
// we provision or send any other requests. They are synchronized by mInitializationLock.
private String mProtocolVersion = null;
private Double mProtocolVersionDouble = null;
private String mDeviceId = null;
private Object mInitializationLock = new Object();
private final String mDeviceType = "Android";
private String mHost;
private String mUsername;
private String mPassword;
private short mConnectionSecurity; private short mConnectionSecurity;
private boolean mSecure; private boolean mSecure;
private HttpClient mHttpClient = null; private HttpClient mHttpClient = null;
private String mAuthString = null; private String mAuthString = null;
private String mCmdString = null;
private HashMap<String, EasFolder> mFolderList = new HashMap<String, EasFolder>(); private HashMap<String, EasFolder> mFolderList = new HashMap<String, EasFolder>();
/**
* eas://user:password@server:port CONNECTION_SECURITY_NONE
* eas+tls://user:password@server:port CONNECTION_SECURITY_TLS_OPTIONAL
* eas+tls+://user:password@server:port CONNECTION_SECURITY_TLS_REQUIRED
* eas+ssl://user:password@server:port CONNECTION_SECURITY_SSL_OPTIONAL
* eas+ssl+://user:password@server:port CONNECTION_SECURITY_SSL_REQUIRED
*/
public EasStore(Account account) throws MessagingException { public EasStore(Account account) throws MessagingException {
super(account); super(account);
ServerSettings settings;
try { try {
mUri = new URI(mAccount.getStoreUri()); settings = decodeUri(mAccount.getStoreUri());
} catch (URISyntaxException use) { } catch (IllegalArgumentException e) {
throw new MessagingException("Invalid WebDavStore URI", use); throw new MessagingException("Error while decoding store URI", e);
} }
String scheme = mUri.getScheme(); mHost = settings.host;
if (scheme.equals("eas")) { mUsername = settings.username;
mPassword = settings.password;
switch (settings.connectionSecurity) {
case NONE:
mConnectionSecurity = CONNECTION_SECURITY_NONE; mConnectionSecurity = CONNECTION_SECURITY_NONE;
} else if (scheme.equals("eas+ssl")) { break;
mConnectionSecurity = CONNECTION_SECURITY_SSL_OPTIONAL; case STARTTLS_OPTIONAL:
} else if (scheme.equals("eas+ssl+")) {
mConnectionSecurity = CONNECTION_SECURITY_SSL_REQUIRED;
} else if (scheme.equals("eas+tls")) {
mConnectionSecurity = CONNECTION_SECURITY_TLS_OPTIONAL; mConnectionSecurity = CONNECTION_SECURITY_TLS_OPTIONAL;
} else if (scheme.equals("eas+tls+")) { break;
case STARTTLS_REQUIRED:
mConnectionSecurity = CONNECTION_SECURITY_TLS_REQUIRED; mConnectionSecurity = CONNECTION_SECURITY_TLS_REQUIRED;
} else { break;
throw new MessagingException("Unsupported protocol"); case SSL_TLS_OPTIONAL:
} mConnectionSecurity = CONNECTION_SECURITY_SSL_OPTIONAL;
break;
mHost = mUri.getHost(); case SSL_TLS_REQUIRED:
if (mHost.startsWith("http")) { mConnectionSecurity = CONNECTION_SECURITY_SSL_REQUIRED;
String[] hostParts = mHost.split("://", 2); break;
if (hostParts.length > 1) {
mHost = hostParts[1];
}
}
if (mUri.getUserInfo() != null) {
try {
String[] userInfoParts = mUri.getUserInfo().split(":");
mUsername = URLDecoder.decode(userInfoParts[0], "UTF-8");
if (userInfoParts.length > 1) {
mPassword = URLDecoder.decode(userInfoParts[1], "UTF-8");
}
} catch (UnsupportedEncodingException enc) {
// This shouldn't happen since the encoding is hardcoded to UTF-8
Log.e(K9.LOG_TAG, "Couldn't urldecode username or password.", enc);
}
} }
mSecure = mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED; mSecure = mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED;
@ -218,37 +321,18 @@ public class EasStore extends Store {
@Override @Override
public void checkSettings() throws MessagingException { public void checkSettings() throws MessagingException {
boolean ssl = true; validateAccount();
boolean trustCertificates = true;
validateAccount(
mHost,
mUsername,
mPassword,
mUri.getPort(),
ssl,
trustCertificates,
K9.app);
} }
public void validateAccount(String hostAddress, String userName, String password, int port, public void validateAccount() throws MessagingException {
boolean ssl, boolean trustCertificates, Context context) throws MessagingException {
try { try {
Log.i(K9.LOG_TAG, "Testing EAS: " + hostAddress + ", " + userName + ", ssl = " + (ssl ? "1" : "0")); Log.i(K9.LOG_TAG, "Testing EAS: " + mHost + ", " + mUsername + ", ssl = " + (mSecure ? "1" : "0"));
// Account account = Preferences.getPreferences(context).newAccount();
// account.setName("%TestAccount%");
// account.setStoreUri(mUri.toString());
EasStore svc = new EasStore(mAccount); EasStore svc = new EasStore(mAccount);
svc.mHost = hostAddress; // We musn't use the "real" device id or we'll screw up current accounts.
svc.mUsername = userName; // Any string will do, but we'll go for "validate".
svc.mPassword = password;
// svc.mSsl = ssl;
// svc.mTrustSsl = trustCertificates;
// We mustn't use the "real" device id or we'll screw up current accounts
// Any string will do, but we'll go for "validate"
svc.mDeviceId = "validate"; svc.mDeviceId = "validate";
HttpResponse resp = svc.sendHttpClientOptions(); HttpResponse resp = svc.sendHttpClientOptions();
reclaimConnection(resp); reclaimConnection(resp);
int code = resp.getStatusLine().getStatusCode(); int code = resp.getStatusLine().getStatusCode();
@ -319,7 +403,7 @@ public class EasStore extends Store {
} }
private String getPolicyType() { private String getPolicyType() {
return (getProtocolVersion() >= return (getProtocolVersionDouble() >=
Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) ? EAS_12_POLICY_TYPE : EAS_2_POLICY_TYPE; Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) ? EAS_12_POLICY_TYPE : EAS_2_POLICY_TYPE;
} }
@ -424,7 +508,7 @@ public class EasStore extends Store {
return (code == HTTP_NEED_PROVISIONING) || (code == HttpStatus.SC_FORBIDDEN); return (code == HTTP_NEED_PROVISIONING) || (code == HttpStatus.SC_FORBIDDEN);
} }
private void setupProtocolVersion(EasStore service, Header versionHeader) private static void setupProtocolVersion(EasStore service, Header versionHeader)
throws MessagingException { throws MessagingException {
// The string is a comma separated list of EAS versions in ascending order // The string is a comma separated list of EAS versions in ascending order
// e.g. 1.0,2.0,2.5,12.0,12.1 // e.g. 1.0,2.0,2.5,12.0,12.1
@ -445,13 +529,15 @@ public class EasStore extends Store {
Log.w(K9.LOG_TAG, "No supported EAS versions: " + supportedVersions); Log.w(K9.LOG_TAG, "No supported EAS versions: " + supportedVersions);
throw new MessagingException("MessagingException.PROTOCOL_VERSION_UNSUPPORTED"); throw new MessagingException("MessagingException.PROTOCOL_VERSION_UNSUPPORTED");
} else { } else {
synchronized (service.mInitializationLock) {
service.mProtocolVersion = ourVersion; service.mProtocolVersion = ourVersion;
service.mProtocolVersionDouble = Double.parseDouble(ourVersion); service.mProtocolVersionDouble = Double.parseDouble(ourVersion);
} }
} }
}
private Double getProtocolVersion() { private Double getProtocolVersionDouble() {
synchronized (mProtocolVersionDouble) { synchronized (mInitializationLock) {
if (mProtocolVersionDouble == null) { if (mProtocolVersionDouble == null) {
try { try {
init(); init();
@ -553,7 +639,9 @@ public class EasStore extends Store {
*/ */
private void setHeaders(HttpRequestBase method, boolean usePolicyKey) { private void setHeaders(HttpRequestBase method, boolean usePolicyKey) {
method.setHeader("Authorization", mAuthString); method.setHeader("Authorization", mAuthString);
synchronized (mInitializationLock) {
method.setHeader("MS-ASProtocolVersion", mProtocolVersion); method.setHeader("MS-ASProtocolVersion", mProtocolVersion);
}
method.setHeader("Connection", "keep-alive"); method.setHeader("Connection", "keep-alive");
method.setHeader("User-Agent", mDeviceType + '/' + Eas.VERSION); method.setHeader("User-Agent", mDeviceType + '/' + Eas.VERSION);
if (usePolicyKey) { if (usePolicyKey) {
@ -598,9 +686,12 @@ public class EasStore extends Store {
String safeUserName = URLEncoder.encode(mUsername); String safeUserName = URLEncoder.encode(mUsername);
String cs = mUsername + ':' + mPassword; String cs = mUsername + ':' + mPassword;
mAuthString = "Basic " + Base64.encodeToString(cs.getBytes(), Base64.NO_WRAP); mAuthString = "Basic " + Base64.encodeToString(cs.getBytes(), Base64.NO_WRAP);
synchronized (mInitializationLock) {
mCmdString = "&User=" + safeUserName + "&DeviceId=" + mDeviceId + mCmdString = "&User=" + safeUserName + "&DeviceId=" + mDeviceId +
"&DeviceType=" + mDeviceType; "&DeviceType=" + mDeviceType;
} }
}
@Override @Override
public List <? extends Folder > getPersonalNamespaces(boolean forceListAll) throws MessagingException { public List <? extends Folder > getPersonalNamespaces(boolean forceListAll) throws MessagingException {
@ -622,6 +713,16 @@ public class EasStore extends Store {
} }
private void init() throws IOException, MessagingException { private void init() throws IOException, MessagingException {
synchronized (mInitializationLock) {
// Get a unique ID to identify the device and application.
if (mDeviceId == null) {
mDeviceId = K9.app.getDeviceId();
if (mDeviceId.length() > MAX_DEVICE_ID_SIZE) {
// This should not happen, since getDeviceId returns a UUID string with the dashes
// removed, which is always 32 characters. Best to be safe.
mDeviceId = mDeviceId.substring(0, MAX_DEVICE_ID_SIZE);
}
}
// Determine our protocol version, if we haven't already and save it in the Account // Determine our protocol version, if we haven't already and save it in the Account
// Also re-check protocol version at least once a day (in case of upgrade) // Also re-check protocol version at least once a day (in case of upgrade)
boolean lastSyncTimeDayDue = false; boolean lastSyncTimeDayDue = false;
@ -649,6 +750,7 @@ public class EasStore extends Store {
} }
} }
} }
}
private List <? extends Folder > getInitialFolderList() throws MessagingException { private List <? extends Folder > getInitialFolderList() throws MessagingException {
LinkedList<Folder> folderList = new LinkedList<Folder>(); LinkedList<Folder> folderList = new LinkedList<Folder>();
@ -1086,7 +1188,7 @@ public class EasStore extends Store {
// Enable MimeSupport // Enable MimeSupport
s.data(Tags.SYNC_MIME_SUPPORT, "2"); s.data(Tags.SYNC_MIME_SUPPORT, "2");
// Set the truncation amount for all classes. // Set the truncation amount for all classes.
if (getProtocolVersion() >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) { if (getProtocolVersionDouble() >= Eas.SUPPORTED_PROTOCOL_EX2007_DOUBLE) {
s.start(Tags.BASE_BODY_PREFERENCE) s.start(Tags.BASE_BODY_PREFERENCE)
// HTML for email; plain text for everything else. // HTML for email; plain text for everything else.
.data(Tags.BASE_TYPE, Eas.BODY_PREFERENCE_MIME); .data(Tags.BASE_TYPE, Eas.BODY_PREFERENCE_MIME);