email: imap push
Change-Id: I8a184a5644e4322ee65d969e14cd47fe119f5df2 Signed-off-by: Jorge Ruesga <jorge@ruesga.com>
This commit is contained in:
parent
3b1b30873e
commit
08ace26ed6
|
@ -31,6 +31,7 @@ import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
|
|
||||||
|
import com.android.emailcommon.service.EmailServiceProxy;
|
||||||
import com.android.emailcommon.utility.Utility;
|
import com.android.emailcommon.utility.Utility;
|
||||||
import com.android.mail.utils.LogUtils;
|
import com.android.mail.utils.LogUtils;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
@ -111,22 +112,26 @@ public final class Account extends EmailContent implements Parcelable {
|
||||||
// Sentinel values for the mSyncInterval field of both Account records
|
// Sentinel values for the mSyncInterval field of both Account records
|
||||||
public static final int CHECK_INTERVAL_NEVER = -1;
|
public static final int CHECK_INTERVAL_NEVER = -1;
|
||||||
public static final int CHECK_INTERVAL_PUSH = -2;
|
public static final int CHECK_INTERVAL_PUSH = -2;
|
||||||
|
public static final int CHECK_INTERVAL_DEFAULT_PULL = 15;
|
||||||
|
|
||||||
public static Uri CONTENT_URI;
|
public static Uri CONTENT_URI;
|
||||||
public static Uri RESET_NEW_MESSAGE_COUNT_URI;
|
public static Uri RESET_NEW_MESSAGE_COUNT_URI;
|
||||||
public static Uri NOTIFIER_URI;
|
public static Uri NOTIFIER_URI;
|
||||||
|
public static Uri SYNC_SETTING_CHANGED_URI;
|
||||||
|
|
||||||
public static void initAccount() {
|
public static void initAccount() {
|
||||||
CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/account");
|
CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/account");
|
||||||
RESET_NEW_MESSAGE_COUNT_URI = Uri.parse(EmailContent.CONTENT_URI + "/resetNewMessageCount");
|
RESET_NEW_MESSAGE_COUNT_URI = Uri.parse(EmailContent.CONTENT_URI + "/resetNewMessageCount");
|
||||||
NOTIFIER_URI = Uri.parse(EmailContent.CONTENT_NOTIFIER_URI + "/account");
|
NOTIFIER_URI = Uri.parse(EmailContent.CONTENT_NOTIFIER_URI + "/account");
|
||||||
|
SYNC_SETTING_CHANGED_URI = Uri.parse(
|
||||||
|
EmailContent.CONTENT_SYNC_SETTING_CHANGED_URI + "/account");
|
||||||
}
|
}
|
||||||
|
|
||||||
public String mDisplayName;
|
public String mDisplayName;
|
||||||
public String mEmailAddress;
|
public String mEmailAddress;
|
||||||
public String mSyncKey;
|
public String mSyncKey;
|
||||||
public int mSyncLookback;
|
public int mSyncLookback;
|
||||||
public int mSyncInterval;
|
private int mSyncInterval;
|
||||||
public long mHostAuthKeyRecv;
|
public long mHostAuthKeyRecv;
|
||||||
public long mHostAuthKeySend;
|
public long mHostAuthKeySend;
|
||||||
public int mFlags;
|
public int mFlags;
|
||||||
|
@ -139,6 +144,7 @@ public final class Account extends EmailContent implements Parcelable {
|
||||||
public String mSignature;
|
public String mSignature;
|
||||||
public long mPolicyKey;
|
public long mPolicyKey;
|
||||||
public long mPingDuration;
|
public long mPingDuration;
|
||||||
|
public int mCapabilities;
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final String JSON_TAG_HOST_AUTH_RECV = "hostAuthRecv";
|
static final String JSON_TAG_HOST_AUTH_RECV = "hostAuthRecv";
|
||||||
|
@ -171,6 +177,7 @@ public final class Account extends EmailContent implements Parcelable {
|
||||||
public static final int CONTENT_POLICY_KEY_COLUMN = 14;
|
public static final int CONTENT_POLICY_KEY_COLUMN = 14;
|
||||||
public static final int CONTENT_PING_DURATION_COLUMN = 15;
|
public static final int CONTENT_PING_DURATION_COLUMN = 15;
|
||||||
public static final int CONTENT_MAX_ATTACHMENT_SIZE_COLUMN = 16;
|
public static final int CONTENT_MAX_ATTACHMENT_SIZE_COLUMN = 16;
|
||||||
|
public static final int CONTENT_CAPABILITIES_COLUMN = 17;
|
||||||
|
|
||||||
public static final String[] CONTENT_PROJECTION = {
|
public static final String[] CONTENT_PROJECTION = {
|
||||||
AttachmentColumns._ID, AccountColumns.DISPLAY_NAME,
|
AttachmentColumns._ID, AccountColumns.DISPLAY_NAME,
|
||||||
|
@ -181,7 +188,7 @@ public final class Account extends EmailContent implements Parcelable {
|
||||||
AccountColumns.RINGTONE_URI, AccountColumns.PROTOCOL_VERSION,
|
AccountColumns.RINGTONE_URI, AccountColumns.PROTOCOL_VERSION,
|
||||||
AccountColumns.SECURITY_SYNC_KEY,
|
AccountColumns.SECURITY_SYNC_KEY,
|
||||||
AccountColumns.SIGNATURE, AccountColumns.POLICY_KEY, AccountColumns.PING_DURATION,
|
AccountColumns.SIGNATURE, AccountColumns.POLICY_KEY, AccountColumns.PING_DURATION,
|
||||||
AccountColumns.MAX_ATTACHMENT_SIZE
|
AccountColumns.MAX_ATTACHMENT_SIZE, AccountColumns.CAPABILITIES
|
||||||
};
|
};
|
||||||
|
|
||||||
public static final int ACCOUNT_FLAGS_COLUMN_ID = 0;
|
public static final int ACCOUNT_FLAGS_COLUMN_ID = 0;
|
||||||
|
@ -279,6 +286,7 @@ public final class Account extends EmailContent implements Parcelable {
|
||||||
mSignature = cursor.getString(CONTENT_SIGNATURE_COLUMN);
|
mSignature = cursor.getString(CONTENT_SIGNATURE_COLUMN);
|
||||||
mPolicyKey = cursor.getLong(CONTENT_POLICY_KEY_COLUMN);
|
mPolicyKey = cursor.getLong(CONTENT_POLICY_KEY_COLUMN);
|
||||||
mPingDuration = cursor.getLong(CONTENT_PING_DURATION_COLUMN);
|
mPingDuration = cursor.getLong(CONTENT_PING_DURATION_COLUMN);
|
||||||
|
mCapabilities = cursor.getInt(CONTENT_CAPABILITIES_COLUMN);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isTemporary() {
|
public boolean isTemporary() {
|
||||||
|
@ -358,6 +366,11 @@ public final class Account extends EmailContent implements Parcelable {
|
||||||
* TODO define sentinel values for "never", "push", etc. See Account.java
|
* TODO define sentinel values for "never", "push", etc. See Account.java
|
||||||
*/
|
*/
|
||||||
public int getSyncInterval() {
|
public int getSyncInterval() {
|
||||||
|
// Fixed unsynced value and account capability. Change to default pull value
|
||||||
|
if (!hasCapability(EmailServiceProxy.CAPABILITY_PUSH)
|
||||||
|
&& mSyncInterval == CHECK_INTERVAL_PUSH) {
|
||||||
|
return CHECK_INTERVAL_DEFAULT_PULL;
|
||||||
|
}
|
||||||
return mSyncInterval;
|
return mSyncInterval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,7 +380,13 @@ public final class Account extends EmailContent implements Parcelable {
|
||||||
* @param minutes the number of minutes between polling checks
|
* @param minutes the number of minutes between polling checks
|
||||||
*/
|
*/
|
||||||
public void setSyncInterval(int minutes) {
|
public void setSyncInterval(int minutes) {
|
||||||
mSyncInterval = minutes;
|
// Fixed unsynced value and account capability. Change to default pull value
|
||||||
|
if (!hasCapability(EmailServiceProxy.CAPABILITY_PUSH)
|
||||||
|
&& mSyncInterval == CHECK_INTERVAL_PUSH) {
|
||||||
|
mSyncInterval = CHECK_INTERVAL_DEFAULT_PULL;
|
||||||
|
} else {
|
||||||
|
mSyncInterval = minutes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -402,6 +421,20 @@ public final class Account extends EmailContent implements Parcelable {
|
||||||
mPingDuration = value;
|
mPingDuration = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the current account capabilities.
|
||||||
|
*/
|
||||||
|
public int getCapabilities() {
|
||||||
|
return mCapabilities;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the account capabilities. Be sure to call save() to commit to database.
|
||||||
|
*/
|
||||||
|
public void setCapabilities(int value) {
|
||||||
|
mCapabilities = value;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the flags for this account
|
* @return the flags for this account
|
||||||
*/
|
*/
|
||||||
|
@ -749,6 +782,7 @@ public final class Account extends EmailContent implements Parcelable {
|
||||||
values.put(AccountColumns.SIGNATURE, mSignature);
|
values.put(AccountColumns.SIGNATURE, mSignature);
|
||||||
values.put(AccountColumns.POLICY_KEY, mPolicyKey);
|
values.put(AccountColumns.POLICY_KEY, mPolicyKey);
|
||||||
values.put(AccountColumns.PING_DURATION, mPingDuration);
|
values.put(AccountColumns.PING_DURATION, mPingDuration);
|
||||||
|
values.put(AccountColumns.CAPABILITIES, mCapabilities);
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -779,6 +813,7 @@ public final class Account extends EmailContent implements Parcelable {
|
||||||
json.putOpt(AccountColumns.PROTOCOL_VERSION, mProtocolVersion);
|
json.putOpt(AccountColumns.PROTOCOL_VERSION, mProtocolVersion);
|
||||||
json.putOpt(AccountColumns.SIGNATURE, mSignature);
|
json.putOpt(AccountColumns.SIGNATURE, mSignature);
|
||||||
json.put(AccountColumns.PING_DURATION, mPingDuration);
|
json.put(AccountColumns.PING_DURATION, mPingDuration);
|
||||||
|
json.put(AccountColumns.CAPABILITIES, mCapabilities);
|
||||||
return json;
|
return json;
|
||||||
} catch (final JSONException e) {
|
} catch (final JSONException e) {
|
||||||
LogUtils.d(LogUtils.TAG, e, "Exception while serializing Account");
|
LogUtils.d(LogUtils.TAG, e, "Exception while serializing Account");
|
||||||
|
@ -817,6 +852,7 @@ public final class Account extends EmailContent implements Parcelable {
|
||||||
a.mSignature = json.optString(AccountColumns.SIGNATURE);
|
a.mSignature = json.optString(AccountColumns.SIGNATURE);
|
||||||
// POLICY_KEY is not stored
|
// POLICY_KEY is not stored
|
||||||
a.mPingDuration = json.optInt(AccountColumns.PING_DURATION, 0);
|
a.mPingDuration = json.optInt(AccountColumns.PING_DURATION, 0);
|
||||||
|
a.mCapabilities = json.optInt(AccountColumns.CAPABILITIES, 0);
|
||||||
return a;
|
return a;
|
||||||
} catch (final JSONException e) {
|
} catch (final JSONException e) {
|
||||||
LogUtils.d(LogUtils.TAG, e, "Exception while deserializing Account");
|
LogUtils.d(LogUtils.TAG, e, "Exception while deserializing Account");
|
||||||
|
@ -842,6 +878,14 @@ public final class Account extends EmailContent implements Parcelable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether or not the capability is supported by the account.
|
||||||
|
* @see EmailServiceProxy#CAPABILITY_*
|
||||||
|
*/
|
||||||
|
public boolean hasCapability(int capability) {
|
||||||
|
return (mCapabilities & capability) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Supports Parcelable
|
* Supports Parcelable
|
||||||
*/
|
*/
|
||||||
|
@ -903,6 +947,7 @@ public final class Account extends EmailContent implements Parcelable {
|
||||||
} else {
|
} else {
|
||||||
dest.writeByte((byte)0);
|
dest.writeByte((byte)0);
|
||||||
}
|
}
|
||||||
|
dest.writeInt(mCapabilities);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -937,6 +982,7 @@ public final class Account extends EmailContent implements Parcelable {
|
||||||
if (in.readByte() == 1) {
|
if (in.readByte() == 1) {
|
||||||
mHostAuthSend = new HostAuth(in);
|
mHostAuthSend = new HostAuth(in);
|
||||||
}
|
}
|
||||||
|
mCapabilities = in.readInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -145,6 +145,8 @@ public abstract class EmailContent {
|
||||||
// delete, or update) and is intended as an optimization for use by clients of message list
|
// delete, or update) and is intended as an optimization for use by clients of message list
|
||||||
// cursors (initially, the email AppWidget).
|
// cursors (initially, the email AppWidget).
|
||||||
public static String NOTIFIER_AUTHORITY;
|
public static String NOTIFIER_AUTHORITY;
|
||||||
|
// The sync settings changed authority is used to notify when a sync setting changed (interval)
|
||||||
|
public static String SYNC_SETTING_CHANGED_AUTHORITY;
|
||||||
public static Uri CONTENT_URI;
|
public static Uri CONTENT_URI;
|
||||||
public static final String PARAMETER_LIMIT = "limit";
|
public static final String PARAMETER_LIMIT = "limit";
|
||||||
|
|
||||||
|
@ -153,6 +155,7 @@ public abstract class EmailContent {
|
||||||
*/
|
*/
|
||||||
public static final String SUPPRESS_COMBINED_ACCOUNT_PARAM = "suppress_combined";
|
public static final String SUPPRESS_COMBINED_ACCOUNT_PARAM = "suppress_combined";
|
||||||
public static Uri CONTENT_NOTIFIER_URI;
|
public static Uri CONTENT_NOTIFIER_URI;
|
||||||
|
public static Uri CONTENT_SYNC_SETTING_CHANGED_URI;
|
||||||
public static Uri PICK_TRASH_FOLDER_URI;
|
public static Uri PICK_TRASH_FOLDER_URI;
|
||||||
public static Uri PICK_SENT_FOLDER_URI;
|
public static Uri PICK_SENT_FOLDER_URI;
|
||||||
public static Uri MAILBOX_NOTIFICATION_URI;
|
public static Uri MAILBOX_NOTIFICATION_URI;
|
||||||
|
@ -175,8 +178,11 @@ public abstract class EmailContent {
|
||||||
AUTHORITY = EMAIL_PACKAGE_NAME + ".provider";
|
AUTHORITY = EMAIL_PACKAGE_NAME + ".provider";
|
||||||
LogUtils.d("EmailContent", "init for " + AUTHORITY);
|
LogUtils.d("EmailContent", "init for " + AUTHORITY);
|
||||||
NOTIFIER_AUTHORITY = EMAIL_PACKAGE_NAME + ".notifier";
|
NOTIFIER_AUTHORITY = EMAIL_PACKAGE_NAME + ".notifier";
|
||||||
|
SYNC_SETTING_CHANGED_AUTHORITY = EMAIL_PACKAGE_NAME + ".sync_setting_changed";
|
||||||
CONTENT_URI = Uri.parse("content://" + AUTHORITY);
|
CONTENT_URI = Uri.parse("content://" + AUTHORITY);
|
||||||
CONTENT_NOTIFIER_URI = Uri.parse("content://" + NOTIFIER_AUTHORITY);
|
CONTENT_NOTIFIER_URI = Uri.parse("content://" + NOTIFIER_AUTHORITY);
|
||||||
|
CONTENT_SYNC_SETTING_CHANGED_URI = Uri.parse(
|
||||||
|
"content://" + SYNC_SETTING_CHANGED_AUTHORITY);
|
||||||
PICK_TRASH_FOLDER_URI = Uri.parse("content://" + AUTHORITY + "/pickTrashFolder");
|
PICK_TRASH_FOLDER_URI = Uri.parse("content://" + AUTHORITY + "/pickTrashFolder");
|
||||||
PICK_SENT_FOLDER_URI = Uri.parse("content://" + AUTHORITY + "/pickSentFolder");
|
PICK_SENT_FOLDER_URI = Uri.parse("content://" + AUTHORITY + "/pickSentFolder");
|
||||||
MAILBOX_NOTIFICATION_URI = Uri.parse("content://" + AUTHORITY + "/mailboxNotification");
|
MAILBOX_NOTIFICATION_URI = Uri.parse("content://" + AUTHORITY + "/mailboxNotification");
|
||||||
|
@ -1724,6 +1730,8 @@ public abstract class EmailContent {
|
||||||
public static final String PING_DURATION = "pingDuration";
|
public static final String PING_DURATION = "pingDuration";
|
||||||
// Automatically fetch pop3 attachments
|
// Automatically fetch pop3 attachments
|
||||||
public static final String AUTO_FETCH_ATTACHMENTS = "autoFetchAttachments";
|
public static final String AUTO_FETCH_ATTACHMENTS = "autoFetchAttachments";
|
||||||
|
// Account capabilities (check EmailServiceProxy#CAPABILITY_*)
|
||||||
|
public static final String CAPABILITIES = "capabilities";
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface QuickResponseColumns extends BaseColumns {
|
public interface QuickResponseColumns extends BaseColumns {
|
||||||
|
|
|
@ -78,10 +78,13 @@ public class Mailbox extends EmailContent implements EmailContent.MailboxColumns
|
||||||
|
|
||||||
public static Uri CONTENT_URI;
|
public static Uri CONTENT_URI;
|
||||||
public static Uri MESSAGE_COUNT_URI;
|
public static Uri MESSAGE_COUNT_URI;
|
||||||
|
public static Uri SYNC_SETTING_CHANGED_URI;
|
||||||
|
|
||||||
public static void initMailbox() {
|
public static void initMailbox() {
|
||||||
CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/mailbox");
|
CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/mailbox");
|
||||||
MESSAGE_COUNT_URI = Uri.parse(EmailContent.CONTENT_URI + "/mailboxCount");
|
MESSAGE_COUNT_URI = Uri.parse(EmailContent.CONTENT_URI + "/mailboxCount");
|
||||||
|
SYNC_SETTING_CHANGED_URI = Uri.parse(
|
||||||
|
EmailContent.CONTENT_SYNC_SETTING_CHANGED_URI + "/mailbox");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String formatMailboxIdExtra(final int index) {
|
private static String formatMailboxIdExtra(final int index) {
|
||||||
|
|
|
@ -68,6 +68,12 @@ public class EmailServiceProxy extends ServiceProxy implements IEmailService {
|
||||||
public static final String VALIDATE_BUNDLE_PROTOCOL_VERSION = "validate_protocol_version";
|
public static final String VALIDATE_BUNDLE_PROTOCOL_VERSION = "validate_protocol_version";
|
||||||
public static final String VALIDATE_BUNDLE_REDIRECT_ADDRESS = "validate_redirect_address";
|
public static final String VALIDATE_BUNDLE_REDIRECT_ADDRESS = "validate_redirect_address";
|
||||||
|
|
||||||
|
// Service capabilities
|
||||||
|
public static final String SETTINGS_BUNDLE_CAPABILITIES = "settings_capabilities";
|
||||||
|
|
||||||
|
// List of common interesting services capabilities
|
||||||
|
public static final int CAPABILITY_PUSH = 1 << 0;
|
||||||
|
|
||||||
private Object mReturn = null;
|
private Object mReturn = null;
|
||||||
private IEmailService mService;
|
private IEmailService mService;
|
||||||
private final boolean isRemote;
|
private final boolean isRemote;
|
||||||
|
|
|
@ -165,6 +165,13 @@ public class EmailConnectivityManager extends BroadcastReceiver {
|
||||||
return info.getType();
|
return info.getType();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static public boolean isConnected(Context context) {
|
||||||
|
ConnectivityManager cm =
|
||||||
|
(ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
NetworkInfo info = cm.getActiveNetworkInfo();
|
||||||
|
return info != null && info.isConnected();
|
||||||
|
}
|
||||||
|
|
||||||
public void waitForConnectivity() {
|
public void waitForConnectivity() {
|
||||||
// If we're unregistered, throw an exception
|
// If we're unregistered, throw an exception
|
||||||
if (!mRegistered) {
|
if (!mRegistered) {
|
||||||
|
|
|
@ -107,7 +107,7 @@ public class AccountSettingsUtils {
|
||||||
cv.put(AccountColumns.DISPLAY_NAME, account.getDisplayName());
|
cv.put(AccountColumns.DISPLAY_NAME, account.getDisplayName());
|
||||||
cv.put(AccountColumns.SENDER_NAME, account.getSenderName());
|
cv.put(AccountColumns.SENDER_NAME, account.getSenderName());
|
||||||
cv.put(AccountColumns.SIGNATURE, account.getSignature());
|
cv.put(AccountColumns.SIGNATURE, account.getSignature());
|
||||||
cv.put(AccountColumns.SYNC_INTERVAL, account.mSyncInterval);
|
cv.put(AccountColumns.SYNC_INTERVAL, account.getSyncInterval());
|
||||||
cv.put(AccountColumns.FLAGS, account.mFlags);
|
cv.put(AccountColumns.FLAGS, account.mFlags);
|
||||||
cv.put(AccountColumns.SYNC_LOOKBACK, account.mSyncLookback);
|
cv.put(AccountColumns.SYNC_LOOKBACK, account.mSyncLookback);
|
||||||
cv.put(AccountColumns.SECURITY_SYNC_KEY, account.mSecuritySyncKey);
|
cv.put(AccountColumns.SECURITY_SYNC_KEY, account.mSecuritySyncKey);
|
||||||
|
|
|
@ -36,6 +36,7 @@ import com.android.emailcommon.mail.MessagingException;
|
||||||
import com.android.mail.utils.LogUtils;
|
import com.android.mail.utils.LogUtils;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -50,6 +51,15 @@ class ImapConnection {
|
||||||
// Always check in FALSE
|
// Always check in FALSE
|
||||||
private static final boolean DEBUG_FORCE_SEND_ID = false;
|
private static final boolean DEBUG_FORCE_SEND_ID = false;
|
||||||
|
|
||||||
|
// RFC 2177 defines that IDLE connections must be refreshed at least every 29 minutes
|
||||||
|
public static final int PING_IDLE_TIMEOUT = 29 * 60 * 1000;
|
||||||
|
|
||||||
|
// Special timeout for DONE operations
|
||||||
|
public static final int DONE_TIMEOUT = 5 * 1000;
|
||||||
|
|
||||||
|
// Time to wait between the first idle message and triggering the changes
|
||||||
|
private static final int IDLE_OP_READ_TIMEOUT = 500;
|
||||||
|
|
||||||
/** ID capability per RFC 2971*/
|
/** ID capability per RFC 2971*/
|
||||||
public static final int CAPABILITY_ID = 1 << 0;
|
public static final int CAPABILITY_ID = 1 << 0;
|
||||||
/** NAMESPACE capability per RFC 2342 */
|
/** NAMESPACE capability per RFC 2342 */
|
||||||
|
@ -58,6 +68,8 @@ class ImapConnection {
|
||||||
public static final int CAPABILITY_STARTTLS = 1 << 2;
|
public static final int CAPABILITY_STARTTLS = 1 << 2;
|
||||||
/** UIDPLUS capability per RFC 4315 */
|
/** UIDPLUS capability per RFC 4315 */
|
||||||
public static final int CAPABILITY_UIDPLUS = 1 << 3;
|
public static final int CAPABILITY_UIDPLUS = 1 << 3;
|
||||||
|
/** IDLE capability per RFC 2177 */
|
||||||
|
public static final int CAPABILITY_IDLE = 1 << 4;
|
||||||
|
|
||||||
/** The capabilities supported; a set of CAPABILITY_* values. */
|
/** The capabilities supported; a set of CAPABILITY_* values. */
|
||||||
private int mCapabilities;
|
private int mCapabilities;
|
||||||
|
@ -69,6 +81,8 @@ class ImapConnection {
|
||||||
private String mAccessToken;
|
private String mAccessToken;
|
||||||
private String mIdPhrase = null;
|
private String mIdPhrase = null;
|
||||||
|
|
||||||
|
private boolean mIdling = false;
|
||||||
|
|
||||||
/** # of command/response lines to log upon crash. */
|
/** # of command/response lines to log upon crash. */
|
||||||
private static final int DISCOURSE_LOGGER_SIZE = 64;
|
private static final int DISCOURSE_LOGGER_SIZE = 64;
|
||||||
private final DiscourseLogger mDiscourse = new DiscourseLogger(DISCOURSE_LOGGER_SIZE);
|
private final DiscourseLogger mDiscourse = new DiscourseLogger(DISCOURSE_LOGGER_SIZE);
|
||||||
|
@ -210,10 +224,23 @@ class ImapConnection {
|
||||||
mImapStore = null;
|
mImapStore = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int getReadTimeout() throws IOException {
|
||||||
|
if (mTransport == null) {
|
||||||
|
return MailTransport.SOCKET_READ_TIMEOUT;
|
||||||
|
}
|
||||||
|
return mTransport.getReadTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setReadTimeout(int timeout) throws IOException {
|
||||||
|
if (mTransport != null) {
|
||||||
|
mTransport.setReadTimeout(timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether or not the specified capability is supported by the server.
|
* Returns whether or not the specified capability is supported by the server.
|
||||||
*/
|
*/
|
||||||
private boolean isCapable(int capability) {
|
public boolean isCapable(int capability) {
|
||||||
return (mCapabilities & capability) != 0;
|
return (mCapabilities & capability) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,6 +262,9 @@ class ImapConnection {
|
||||||
if (capabilities.contains(ImapConstants.STARTTLS)) {
|
if (capabilities.contains(ImapConstants.STARTTLS)) {
|
||||||
mCapabilities |= CAPABILITY_STARTTLS;
|
mCapabilities |= CAPABILITY_STARTTLS;
|
||||||
}
|
}
|
||||||
|
if (capabilities.contains(ImapConstants.IDLE)) {
|
||||||
|
mCapabilities |= CAPABILITY_IDLE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -273,6 +303,12 @@ class ImapConnection {
|
||||||
*/
|
*/
|
||||||
String sendCommand(String command, boolean sensitive)
|
String sendCommand(String command, boolean sensitive)
|
||||||
throws MessagingException, IOException {
|
throws MessagingException, IOException {
|
||||||
|
// Don't allow any command other than DONE when idling
|
||||||
|
if (mIdling && !command.equals(ImapConstants.DONE)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
mIdling = command.equals(ImapConstants.IDLE);
|
||||||
|
|
||||||
LogUtils.d(Logging.LOG_TAG, "sendCommand %s", (sensitive ? IMAP_REDACTED_LOG : command));
|
LogUtils.d(Logging.LOG_TAG, "sendCommand %s", (sensitive ? IMAP_REDACTED_LOG : command));
|
||||||
open();
|
open();
|
||||||
return sendCommandInternal(command, sensitive);
|
return sendCommandInternal(command, sensitive);
|
||||||
|
@ -284,7 +320,13 @@ class ImapConnection {
|
||||||
throw new IOException("Null transport");
|
throw new IOException("Null transport");
|
||||||
}
|
}
|
||||||
String tag = Integer.toString(mNextCommandTag.incrementAndGet());
|
String tag = Integer.toString(mNextCommandTag.incrementAndGet());
|
||||||
String commandToSend = tag + " " + command;
|
final String commandToSend;
|
||||||
|
if (command.equals(ImapConstants.DONE)) {
|
||||||
|
// Do not send a tag for DONE command
|
||||||
|
commandToSend = command;
|
||||||
|
} else {
|
||||||
|
commandToSend = tag + " " + command;
|
||||||
|
}
|
||||||
mTransport.writeLine(commandToSend, sensitive ? IMAP_REDACTED_LOG : null);
|
mTransport.writeLine(commandToSend, sensitive ? IMAP_REDACTED_LOG : null);
|
||||||
mDiscourse.addSentCommand(sensitive ? IMAP_REDACTED_LOG : commandToSend);
|
mDiscourse.addSentCommand(sensitive ? IMAP_REDACTED_LOG : commandToSend);
|
||||||
return tag;
|
return tag;
|
||||||
|
@ -327,6 +369,11 @@ class ImapConnection {
|
||||||
return executeSimpleCommand(command, false);
|
return executeSimpleCommand(command, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<ImapResponse> executeIdleCommand() throws IOException, MessagingException {
|
||||||
|
mParser.expectIdlingResponse();
|
||||||
|
return executeSimpleCommand(ImapConstants.IDLE, false);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read and return all of the responses from the most recent command sent to the server
|
* Read and return all of the responses from the most recent command sent to the server
|
||||||
*
|
*
|
||||||
|
@ -336,13 +383,35 @@ class ImapConnection {
|
||||||
*/
|
*/
|
||||||
List<ImapResponse> getCommandResponses() throws IOException, MessagingException {
|
List<ImapResponse> getCommandResponses() throws IOException, MessagingException {
|
||||||
final List<ImapResponse> responses = new ArrayList<ImapResponse>();
|
final List<ImapResponse> responses = new ArrayList<ImapResponse>();
|
||||||
ImapResponse response;
|
ImapResponse response = null;
|
||||||
do {
|
boolean idling = false;
|
||||||
response = mParser.readResponse();
|
boolean throwSocketTimeoutEx = true;
|
||||||
responses.add(response);
|
int lastSocketTimeout = getReadTimeout();
|
||||||
} while (!response.isTagged());
|
try {
|
||||||
|
do {
|
||||||
|
response = mParser.readResponse();
|
||||||
|
if (idling) {
|
||||||
|
setReadTimeout(IDLE_OP_READ_TIMEOUT);
|
||||||
|
throwSocketTimeoutEx = false;
|
||||||
|
}
|
||||||
|
responses.add(response);
|
||||||
|
if (response.isIdling()) {
|
||||||
|
idling = true;
|
||||||
|
}
|
||||||
|
} while (idling || !response.isTagged());
|
||||||
|
} catch (SocketTimeoutException ex) {
|
||||||
|
if (throwSocketTimeoutEx) {
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
mParser.resetIdlingStatus();
|
||||||
|
if (lastSocketTimeout != getReadTimeout()) {
|
||||||
|
setReadTimeout(lastSocketTimeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.isOk()) {
|
// When idling, any response is valid; otherwise it must be OK
|
||||||
|
if (!response.isOk() && !idling) {
|
||||||
final String toString = response.toString();
|
final String toString = response.toString();
|
||||||
final String status = response.getStatusOrEmpty().getString();
|
final String status = response.getStatusOrEmpty().getString();
|
||||||
final String alert = response.getAlertTextOrEmpty().getString();
|
final String alert = response.getAlertTextOrEmpty().getString();
|
||||||
|
|
|
@ -52,6 +52,8 @@ import com.android.emailcommon.utility.Utility;
|
||||||
import com.android.mail.utils.LogUtils;
|
import com.android.mail.utils.LogUtils;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
|
||||||
|
import static com.android.emailcommon.Logging.LOG_TAG;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -60,6 +62,7 @@ import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -68,13 +71,39 @@ import java.util.HashMap;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
class ImapFolder extends Folder {
|
public class ImapFolder extends Folder {
|
||||||
private final static Flag[] PERMANENT_FLAGS =
|
private final static Flag[] PERMANENT_FLAGS =
|
||||||
{ Flag.DELETED, Flag.SEEN, Flag.FLAGGED, Flag.ANSWERED };
|
{ Flag.DELETED, Flag.SEEN, Flag.FLAGGED, Flag.ANSWERED };
|
||||||
private static final int COPY_BUFFER_SIZE = 16*1024;
|
private static final int COPY_BUFFER_SIZE = 16*1024;
|
||||||
|
|
||||||
|
public interface IdleCallback {
|
||||||
|
/**
|
||||||
|
* Invoked when the connection enters idle mode
|
||||||
|
*/
|
||||||
|
public void onIdled();
|
||||||
|
/**
|
||||||
|
* Invoked when a new change is communicated by the server.
|
||||||
|
*
|
||||||
|
* @param needSync whether a sync is required
|
||||||
|
* @param fetchMessages list of message UIDs to update
|
||||||
|
*/
|
||||||
|
public void onNewServerChange(boolean needSync, List<String> fetchMessages);
|
||||||
|
/**
|
||||||
|
* Connection to socket timed out. The idle connection needs
|
||||||
|
* to be considered broken when this is called.
|
||||||
|
*/
|
||||||
|
public void onTimeout();
|
||||||
|
/**
|
||||||
|
* Something went wrong while waiting for push data.
|
||||||
|
*
|
||||||
|
* @param ex the exception detected
|
||||||
|
*/
|
||||||
|
public void onException(MessagingException ex);
|
||||||
|
}
|
||||||
|
|
||||||
private final ImapStore mStore;
|
private final ImapStore mStore;
|
||||||
private final String mName;
|
private final String mName;
|
||||||
private int mMessageCount = -1;
|
private int mMessageCount = -1;
|
||||||
|
@ -86,6 +115,22 @@ class ImapFolder extends Folder {
|
||||||
/** A set of hashes that can be used to track dirtiness */
|
/** A set of hashes that can be used to track dirtiness */
|
||||||
Object mHash[];
|
Object mHash[];
|
||||||
|
|
||||||
|
private final Object mIdleSync = new Object();
|
||||||
|
private boolean mIdling;
|
||||||
|
private boolean mIdlingCancelled;
|
||||||
|
private boolean mDiscardIdlingConnection;
|
||||||
|
private Thread mIdleReader;
|
||||||
|
|
||||||
|
private static final String[] IDLE_STATUSES = {
|
||||||
|
ImapConstants.UIDVALIDITY, ImapConstants.UIDNEXT
|
||||||
|
};
|
||||||
|
private Map<String, String> mIdleStatuses = new HashMap<>();
|
||||||
|
|
||||||
|
private static class ImapIdleChanges {
|
||||||
|
public boolean mRequiredSync = false;
|
||||||
|
public ArrayList<String> mMessageToFetch = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
/*package*/ ImapFolder(ImapStore store, String name) {
|
/*package*/ ImapFolder(ImapStore store, String name) {
|
||||||
mStore = store;
|
mStore = store;
|
||||||
mName = name;
|
mName = name;
|
||||||
|
@ -176,6 +221,159 @@ class ImapFolder extends Folder {
|
||||||
return mName;
|
return mName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void startIdling(final IdleCallback callback) throws MessagingException {
|
||||||
|
checkOpen();
|
||||||
|
synchronized (mIdleSync) {
|
||||||
|
if (mIdling) {
|
||||||
|
throw new MessagingException("Folder " + mName + " is in IDLE state already.");
|
||||||
|
}
|
||||||
|
mIdling = true;
|
||||||
|
mIdlingCancelled = false;
|
||||||
|
mDiscardIdlingConnection = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run idle in background
|
||||||
|
mIdleReader = new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
// Get some info before start idling
|
||||||
|
mIdleStatuses = getStatuses(IDLE_STATUSES);
|
||||||
|
|
||||||
|
// We setup the max time specified in RFC 2177 to re-issue
|
||||||
|
// an idle request to the server
|
||||||
|
mConnection.setReadTimeout(ImapConnection.PING_IDLE_TIMEOUT);
|
||||||
|
mConnection.destroyResponses();
|
||||||
|
|
||||||
|
// Enter now in idle status (we hold a connection with
|
||||||
|
// the server to listen for new changes)
|
||||||
|
synchronized (mIdleSync) {
|
||||||
|
if (mIdlingCancelled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onIdled();
|
||||||
|
}
|
||||||
|
List<ImapResponse> responses = mConnection.executeIdleCommand();
|
||||||
|
|
||||||
|
// Check whether IDLE was successful (first response is an idling response)
|
||||||
|
if (responses.isEmpty() || (mIdling && !responses.get(0).isIdling())) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onException(new MessagingException(
|
||||||
|
MessagingException.SERVER_ERROR, "Cannot idle"));
|
||||||
|
}
|
||||||
|
synchronized (mIdleSync) {
|
||||||
|
mIdling = false;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit idle if we are still in that state
|
||||||
|
boolean cancelled = false;
|
||||||
|
boolean discardConnection = false;
|
||||||
|
synchronized (mIdleSync) {
|
||||||
|
if (!mIdlingCancelled) {
|
||||||
|
try {
|
||||||
|
mConnection.setReadTimeout(ImapConnection.DONE_TIMEOUT);
|
||||||
|
mConnection.executeSimpleCommand(ImapConstants.DONE);
|
||||||
|
} catch (MessagingException me) {
|
||||||
|
// Ignore this exception caused by messages in the queue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelled = mIdlingCancelled;
|
||||||
|
discardConnection = mDiscardIdlingConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cancelled && callback != null) {
|
||||||
|
// Notify that new changes exists in the server. Remove
|
||||||
|
// the idling status response since is only relevant for the protocol
|
||||||
|
// We have to enter in idle
|
||||||
|
ImapIdleChanges changes = extractImapChanges(
|
||||||
|
new ArrayList<Object>(responses.subList(1, responses.size())));
|
||||||
|
callback.onNewServerChange(changes.mRequiredSync, changes.mMessageToFetch);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discardConnection) {
|
||||||
|
// Return the connection to the pool
|
||||||
|
close(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (mIdleSync) {
|
||||||
|
mIdling = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (MessagingException me) {
|
||||||
|
close(false);
|
||||||
|
synchronized (mIdleSync) {
|
||||||
|
mIdling = false;
|
||||||
|
}
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onException(me);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (SocketTimeoutException ste) {
|
||||||
|
close(false);
|
||||||
|
synchronized (mIdleSync) {
|
||||||
|
mIdling = false;
|
||||||
|
}
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
close(false);
|
||||||
|
synchronized (mIdleSync) {
|
||||||
|
mIdling = false;
|
||||||
|
}
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onException(ioExceptionHandler(mConnection, ioe));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mIdleReader.setName("IdleReader " + mStore.getAccount().mId + ":" + mName);
|
||||||
|
mIdleReader.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stopIdling(boolean discardConnection) throws MessagingException {
|
||||||
|
if (!isOpen()) {
|
||||||
|
throw new MessagingException("Folder " + mName + " is not open.");
|
||||||
|
}
|
||||||
|
synchronized (mIdleSync) {
|
||||||
|
if (!mIdling) {
|
||||||
|
throw new MessagingException("Folder " + mName + " isn't in IDLE state.");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
mIdlingCancelled = true;
|
||||||
|
mDiscardIdlingConnection = discardConnection;
|
||||||
|
// We can read responses here because we can block the buffer. Read commands
|
||||||
|
// are always done by startListener method (blocking idle)
|
||||||
|
mConnection.sendCommand(ImapConstants.DONE, false);
|
||||||
|
|
||||||
|
} catch (MessagingException me) {
|
||||||
|
// Treat IOERROR messaging exception as IOException
|
||||||
|
if (me.getExceptionType() == MessagingException.IOERROR) {
|
||||||
|
close(false);
|
||||||
|
throw me;
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
throw ioExceptionHandler(mConnection, ioe);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isIdling() {
|
||||||
|
synchronized (mIdleSync) {
|
||||||
|
return mIdling;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean exists() throws MessagingException {
|
public boolean exists() throws MessagingException {
|
||||||
if (mExists) {
|
if (mExists) {
|
||||||
|
@ -373,6 +571,58 @@ class ImapFolder extends Folder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getStatuses(String[] statuses) throws MessagingException {
|
||||||
|
checkOpen();
|
||||||
|
Map<String, String> allReturnStatuses = new HashMap<>();
|
||||||
|
try {
|
||||||
|
String flags = TextUtils.join(" ", statuses);
|
||||||
|
final List<ImapResponse> responses = mConnection.executeSimpleCommand(
|
||||||
|
String.format(Locale.US,
|
||||||
|
ImapConstants.STATUS + " \"%s\" (%s)",
|
||||||
|
ImapStore.encodeFolderName(mName, mStore.mPathPrefix), flags));
|
||||||
|
// S: * STATUS mboxname (MESSAGES 231 UIDNEXT 44292)
|
||||||
|
for (ImapResponse response : responses) {
|
||||||
|
if (response.isDataResponse(0, ImapConstants.STATUS)) {
|
||||||
|
ImapList list = response.getListOrEmpty(2);
|
||||||
|
int count = list.size();
|
||||||
|
for (int i = 0; i < count; i += 2) {
|
||||||
|
String key = list.getStringOrEmpty(i).getString();
|
||||||
|
String value = list.getStringOrEmpty(i + 1).getString();
|
||||||
|
allReturnStatuses.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
throw ioExceptionHandler(mConnection, ioe);
|
||||||
|
} finally {
|
||||||
|
destroyResponses();
|
||||||
|
}
|
||||||
|
return allReturnStatuses;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getNewMessagesFromUid(String uid) throws MessagingException {
|
||||||
|
checkOpen();
|
||||||
|
List<String> nextMSNs = new ArrayList<>();
|
||||||
|
try {
|
||||||
|
final List<ImapResponse> responses = mConnection.executeSimpleCommand(
|
||||||
|
ImapConstants.SEARCH + " " + ImapConstants.UID + " " + uid + ":*");
|
||||||
|
// S: * SEARCH 1 2 3
|
||||||
|
for (ImapResponse response : responses) {
|
||||||
|
if (response.isDataResponse(0, ImapConstants.SEARCH)) {
|
||||||
|
int count = response.size();
|
||||||
|
for (int i = 1; i < count; i++) {
|
||||||
|
nextMSNs.add(response.getStringOrEmpty(i).getString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
throw ioExceptionHandler(mConnection, ioe);
|
||||||
|
} finally {
|
||||||
|
destroyResponses();
|
||||||
|
}
|
||||||
|
return nextMSNs;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete(boolean recurse) {
|
public void delete(boolean recurse) {
|
||||||
throw new Error("ImapStore.delete() not yet implemented");
|
throw new Error("ImapStore.delete() not yet implemented");
|
||||||
|
@ -1270,7 +1520,9 @@ class ImapFolder extends Folder {
|
||||||
if (DebugUtils.DEBUG) {
|
if (DebugUtils.DEBUG) {
|
||||||
LogUtils.d(Logging.LOG_TAG, "IO Exception detected: ", ioe);
|
LogUtils.d(Logging.LOG_TAG, "IO Exception detected: ", ioe);
|
||||||
}
|
}
|
||||||
connection.close();
|
if (connection != null) {
|
||||||
|
connection.close();
|
||||||
|
}
|
||||||
if (connection == mConnection) {
|
if (connection == mConnection) {
|
||||||
mConnection = null; // To prevent close() from returning the connection to the pool.
|
mConnection = null; // To prevent close() from returning the connection to the pool.
|
||||||
close(false);
|
close(false);
|
||||||
|
@ -1278,6 +1530,127 @@ class ImapFolder extends Folder {
|
||||||
return new MessagingException(MessagingException.IOERROR, "IO Error", ioe);
|
return new MessagingException(MessagingException.IOERROR, "IO Error", ioe);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ImapIdleChanges extractImapChanges(List<Object> changes) throws MessagingException {
|
||||||
|
// Process the changes and fill the idle changes structure.
|
||||||
|
// Basically we should look for the next commands in this method:
|
||||||
|
//
|
||||||
|
// OK DONE
|
||||||
|
// No more changes
|
||||||
|
// n EXISTS
|
||||||
|
// Indicates that the mailbox changed => ignore
|
||||||
|
// n EXPUNGE
|
||||||
|
// Indicates a message were completely deleted => a full sync is required
|
||||||
|
// n RECENT
|
||||||
|
// New messages waiting in the server => use UIDNEXT to search for the new messages.
|
||||||
|
// If isn't possible to retrieve the new UID messages, then a full sync is required
|
||||||
|
// n FETCH (UID X FLAGS (...))
|
||||||
|
// a message has changed and requires to fetch only X message
|
||||||
|
// (something change on that item). If UID is not present, a conversion
|
||||||
|
// from MSN to UID is required
|
||||||
|
|
||||||
|
final ImapIdleChanges imapIdleChanges = new ImapIdleChanges();
|
||||||
|
|
||||||
|
int count = changes.size();
|
||||||
|
if (Logging.LOGD) {
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
ImapResponse change = (ImapResponse) changes.get(i);
|
||||||
|
if (Logging.LOGD) {
|
||||||
|
LogUtils.d(Logging.LOG_TAG, "Received: " + change.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We can't ask to the server, because the responses will be destroyed. We need
|
||||||
|
// to compute and fetch any related after we have all the responses processed
|
||||||
|
boolean hasNewMessages = false;
|
||||||
|
List<String> msns = new ArrayList<>();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
ImapResponse change = (ImapResponse) changes.get(i);
|
||||||
|
if (change.isOk() || change.isNo() || change.isBad()) {
|
||||||
|
// No more processing. DONE included
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
ImapElement element = change.getElementOrNone(1);
|
||||||
|
if (element.equals(ImapElement.NONE)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!element.isString()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImapString op = (ImapString) element;
|
||||||
|
if (op.is(ImapConstants.DONE)) {
|
||||||
|
break;
|
||||||
|
} else if (op.is(ImapConstants.EXISTS)) {
|
||||||
|
continue;
|
||||||
|
} else if (op.is(ImapConstants.EXPUNGE)) {
|
||||||
|
imapIdleChanges.mRequiredSync = true;
|
||||||
|
} else if (op.is(ImapConstants.RECENT)) {
|
||||||
|
hasNewMessages = true;
|
||||||
|
} else if (op.is(ImapConstants.FETCH)
|
||||||
|
&& change.getElementOrNone(2).isList()) {
|
||||||
|
ImapList messageFlags = (ImapList) change.getElementOrNone(2);
|
||||||
|
String uid = ((ImapString) messageFlags.getKeyedStringOrEmpty(
|
||||||
|
ImapConstants.UID, true)).getString();
|
||||||
|
if (!TextUtils.isEmpty(uid) &&
|
||||||
|
!imapIdleChanges.mMessageToFetch.contains(uid)) {
|
||||||
|
imapIdleChanges.mMessageToFetch.add(uid);
|
||||||
|
} else {
|
||||||
|
msns.add(change.getStringOrEmpty(0).getString());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (Logging.LOGD) {
|
||||||
|
LogUtils.w(LOG_TAG, "Unrecognized imap change (" + change
|
||||||
|
+ ") for mailbox " + mName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception ex) {
|
||||||
|
if (Logging.LOGD) {
|
||||||
|
LogUtils.e(LOG_TAG, "Failure processing imap change (" + change
|
||||||
|
+ ") for mailbox " + mName, ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether UIDVALIDITY changed - if yes, a full sync request is required
|
||||||
|
// NOTE: This needs to happen after parsing all responses; otherwise
|
||||||
|
// getStatuses will destroy the response
|
||||||
|
Map<String, String> statuses = getStatuses(new String[] { ImapConstants.UIDVALIDITY });
|
||||||
|
String oldUidValidity = mIdleStatuses.get(ImapConstants.UIDVALIDITY);
|
||||||
|
String newUidValidity = statuses.get(ImapConstants.UIDVALIDITY);
|
||||||
|
if (!TextUtils.equals(oldUidValidity, newUidValidity)) {
|
||||||
|
imapIdleChanges.mMessageToFetch.clear();
|
||||||
|
imapIdleChanges.mRequiredSync = true;
|
||||||
|
return imapIdleChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recover the UIDs of new messages in case we don't do a full sync anyway
|
||||||
|
if (!imapIdleChanges.mRequiredSync) {
|
||||||
|
try {
|
||||||
|
// Retrieve new message UIDs
|
||||||
|
String uidNext = mIdleStatuses.get(ImapConstants.UIDNEXT);
|
||||||
|
if (hasNewMessages && !TextUtils.isEmpty(uidNext)) {
|
||||||
|
msns.addAll(getNewMessagesFromUid(uidNext));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform MSNs to UIDs
|
||||||
|
for (String msn : msns) {
|
||||||
|
String[] uids = searchForUids(String.format(Locale.US, "%s:%s", msn, msn));
|
||||||
|
imapIdleChanges.mMessageToFetch.add(uids[0]);
|
||||||
|
}
|
||||||
|
} catch (MessagingException ex) {
|
||||||
|
// Server doesn't support UID. We have to do a full sync (since
|
||||||
|
// we don't know what message changed)
|
||||||
|
imapIdleChanges.mMessageToFetch.clear();
|
||||||
|
imapIdleChanges.mRequiredSync = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return imapIdleChanges;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (o instanceof ImapFolder) {
|
if (o instanceof ImapFolder) {
|
||||||
|
|
|
@ -501,6 +501,14 @@ public class ImapStore extends Store {
|
||||||
connection.destroyResponses();
|
connection.destroyResponses();
|
||||||
}
|
}
|
||||||
bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, result);
|
bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, result);
|
||||||
|
|
||||||
|
// Shared capabilities (check EmailProxyServices for available shared capabilities)
|
||||||
|
int capabilities = 0;
|
||||||
|
if (connection.isCapable(ImapConnection.CAPABILITY_IDLE)) {
|
||||||
|
capabilities |= EmailServiceProxy.CAPABILITY_PUSH;
|
||||||
|
}
|
||||||
|
bundle.putInt(EmailServiceProxy.SETTINGS_BUNDLE_CAPABILITIES, capabilities);
|
||||||
|
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -556,6 +564,7 @@ public class ImapStore extends Store {
|
||||||
while ((connection = mConnectionPool.poll()) != null) {
|
while ((connection = mConnectionPool.poll()) != null) {
|
||||||
try {
|
try {
|
||||||
connection.setStore(this);
|
connection.setStore(this);
|
||||||
|
connection.setReadTimeout(MailTransport.SOCKET_READ_TIMEOUT);
|
||||||
connection.executeSimpleCommand(ImapConstants.NOOP);
|
connection.executeSimpleCommand(ImapConstants.NOOP);
|
||||||
break;
|
break;
|
||||||
} catch (MessagingException e) {
|
} catch (MessagingException e) {
|
||||||
|
|
|
@ -186,6 +186,10 @@ public class Pop3Store extends Store {
|
||||||
ioe.getMessage());
|
ioe.getMessage());
|
||||||
}
|
}
|
||||||
bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, result);
|
bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, result);
|
||||||
|
|
||||||
|
// No special capabilities
|
||||||
|
bundle.putInt(EmailServiceProxy.SETTINGS_BUNDLE_CAPABILITIES, 0);
|
||||||
|
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,7 @@ public final class ImapConstants {
|
||||||
public static final String COPYUID = "COPYUID";
|
public static final String COPYUID = "COPYUID";
|
||||||
public static final String CREATE = "CREATE";
|
public static final String CREATE = "CREATE";
|
||||||
public static final String DELETE = "DELETE";
|
public static final String DELETE = "DELETE";
|
||||||
|
public static final String DONE = "DONE";
|
||||||
public static final String EXAMINE = "EXAMINE";
|
public static final String EXAMINE = "EXAMINE";
|
||||||
public static final String EXISTS = "EXISTS";
|
public static final String EXISTS = "EXISTS";
|
||||||
public static final String EXPUNGE = "EXPUNGE";
|
public static final String EXPUNGE = "EXPUNGE";
|
||||||
|
@ -58,6 +59,8 @@ public final class ImapConstants {
|
||||||
public static final String FLAGS = "FLAGS";
|
public static final String FLAGS = "FLAGS";
|
||||||
public static final String FLAGS_SILENT = "FLAGS.SILENT";
|
public static final String FLAGS_SILENT = "FLAGS.SILENT";
|
||||||
public static final String ID = "ID";
|
public static final String ID = "ID";
|
||||||
|
public static final String IDLE = "IDLE";
|
||||||
|
public static final String IDLING = "idling";
|
||||||
public static final String INBOX = "INBOX";
|
public static final String INBOX = "INBOX";
|
||||||
public static final String INTERNALDATE = "INTERNALDATE";
|
public static final String INTERNALDATE = "INTERNALDATE";
|
||||||
public static final String LIST = "LIST";
|
public static final String LIST = "LIST";
|
||||||
|
@ -73,6 +76,7 @@ public final class ImapConstants {
|
||||||
public static final String PREAUTH = "PREAUTH";
|
public static final String PREAUTH = "PREAUTH";
|
||||||
public static final String READ_ONLY = "READ-ONLY";
|
public static final String READ_ONLY = "READ-ONLY";
|
||||||
public static final String READ_WRITE = "READ-WRITE";
|
public static final String READ_WRITE = "READ-WRITE";
|
||||||
|
public static final String RECENT = "RECENT";
|
||||||
public static final String RENAME = "RENAME";
|
public static final String RENAME = "RENAME";
|
||||||
public static final String RFC822_SIZE = "RFC822.SIZE";
|
public static final String RFC822_SIZE = "RFC822.SIZE";
|
||||||
public static final String SEARCH = "SEARCH";
|
public static final String SEARCH = "SEARCH";
|
||||||
|
|
|
@ -180,7 +180,7 @@ public class ImapList extends ImapElement {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return mList.toString();
|
return mList != null ? mList.toString() : "[null]";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -76,6 +76,13 @@ public class ImapResponse extends ImapList {
|
||||||
return is(0, ImapConstants.NO);
|
return is(0, ImapConstants.NO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether it's an IDLE response.
|
||||||
|
*/
|
||||||
|
public boolean isIdling() {
|
||||||
|
return is(0, ImapConstants.IDLING);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return whether it's an {@code responseType} data response. (i.e. not tagged).
|
* @return whether it's an {@code responseType} data response. (i.e. not tagged).
|
||||||
* @param index where {@code responseType} should appear. e.g. 1 for "FETCH"
|
* @param index where {@code responseType} should appear. e.g. 1 for "FETCH"
|
||||||
|
|
|
@ -66,6 +66,9 @@ public class ImapResponseParser {
|
||||||
*/
|
*/
|
||||||
private final ArrayList<ImapResponse> mResponsesToDestroy = new ArrayList<ImapResponse>();
|
private final ArrayList<ImapResponse> mResponsesToDestroy = new ArrayList<ImapResponse>();
|
||||||
|
|
||||||
|
private boolean mIdling;
|
||||||
|
private boolean mExpectIdlingResponse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exception thrown when we receive BYE. It derives from IOException, so it'll be treated
|
* Exception thrown when we receive BYE. It derives from IOException, so it'll be treated
|
||||||
* in the same way EOF does.
|
* in the same way EOF does.
|
||||||
|
@ -168,10 +171,17 @@ public class ImapResponseParser {
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
// Parser crash -- log network activities.
|
// Parser crash -- log network activities.
|
||||||
onParseError(e);
|
onParseError(e);
|
||||||
|
mIdling = false;
|
||||||
throw e;
|
throw e;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
// Network error, or received an unexpected char.
|
// Network error, or received an unexpected char.
|
||||||
onParseError(e);
|
// If we are idling don't parse the error, just let the upper layers
|
||||||
|
// handle the exception
|
||||||
|
if (!mIdling) {
|
||||||
|
onParseError(e);
|
||||||
|
} else {
|
||||||
|
mIdling = false;
|
||||||
|
}
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -242,6 +252,14 @@ public class ImapResponseParser {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void resetIdlingStatus() {
|
||||||
|
mIdling = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void expectIdlingResponse() {
|
||||||
|
mExpectIdlingResponse = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse and return the response line.
|
* Parse and return the response line.
|
||||||
*/
|
*/
|
||||||
|
@ -263,11 +281,26 @@ public class ImapResponseParser {
|
||||||
responseToDestroy = new ImapResponse(null, true);
|
responseToDestroy = new ImapResponse(null, true);
|
||||||
|
|
||||||
// If it's continuation request, we don't really care what's in it.
|
// If it's continuation request, we don't really care what's in it.
|
||||||
responseToDestroy.add(new ImapSimpleString(readUntilEol()));
|
// NOTE: specs say the server is supposed to respond to the IDLE command
|
||||||
|
// with a continuation request response. To simplify internal handling,
|
||||||
|
// we'll always construct same response (ignoring the server text response).
|
||||||
|
// Our implementation always returns "+ idling".
|
||||||
|
if (mExpectIdlingResponse) {
|
||||||
|
// Discard the server message and put what we expected
|
||||||
|
readUntilEol();
|
||||||
|
responseToDestroy.add(new ImapSimpleString(ImapConstants.IDLING));
|
||||||
|
} else {
|
||||||
|
responseToDestroy.add(new ImapSimpleString(readUntilEol()));
|
||||||
|
}
|
||||||
|
|
||||||
// Response has successfully been built. Let's return it.
|
// Response has successfully been built. Let's return it.
|
||||||
responseToReturn = responseToDestroy;
|
responseToReturn = responseToDestroy;
|
||||||
responseToDestroy = null;
|
responseToDestroy = null;
|
||||||
|
|
||||||
|
mIdling = responseToReturn.isIdling();
|
||||||
|
if (mIdling) {
|
||||||
|
mExpectIdlingResponse = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Status response or response data
|
// Status response or response data
|
||||||
final String tag;
|
final String tag;
|
||||||
|
|
|
@ -191,6 +191,14 @@ public class MailTransport {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getReadTimeout() throws IOException {
|
||||||
|
return mSocket.getSoTimeout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setReadTimeout(int timeout) throws IOException {
|
||||||
|
mSocket.setSoTimeout(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Lightweight version of SSLCertificateSocketFactory.verifyHostname, which provides this
|
* Lightweight version of SSLCertificateSocketFactory.verifyHostname, which provides this
|
||||||
* service but is not in the public API.
|
* service but is not in the public API.
|
||||||
|
|
|
@ -58,6 +58,7 @@ import com.android.emailcommon.provider.MessageStateChange;
|
||||||
import com.android.emailcommon.provider.Policy;
|
import com.android.emailcommon.provider.Policy;
|
||||||
import com.android.emailcommon.provider.QuickResponse;
|
import com.android.emailcommon.provider.QuickResponse;
|
||||||
import com.android.emailcommon.provider.SuggestedContact;
|
import com.android.emailcommon.provider.SuggestedContact;
|
||||||
|
import com.android.emailcommon.service.EmailServiceProxy;
|
||||||
import com.android.emailcommon.service.LegacyPolicySet;
|
import com.android.emailcommon.service.LegacyPolicySet;
|
||||||
import com.android.emailcommon.service.SyncWindow;
|
import com.android.emailcommon.service.SyncWindow;
|
||||||
import com.android.mail.providers.UIProvider;
|
import com.android.mail.providers.UIProvider;
|
||||||
|
@ -187,7 +188,8 @@ public final class DBHelper {
|
||||||
// Version 127: Force mFlags to contain the correct flags for EAS accounts given a protocol
|
// Version 127: Force mFlags to contain the correct flags for EAS accounts given a protocol
|
||||||
// version above 12.0
|
// version above 12.0
|
||||||
// Version 129: Update all IMAP INBOX mailboxes to force synchronization
|
// Version 129: Update all IMAP INBOX mailboxes to force synchronization
|
||||||
public static final int DATABASE_VERSION = 129;
|
// Version 130: Account capabilities (check EmailServiceProxy#CAPABILITY_*)
|
||||||
|
public static final int DATABASE_VERSION = 130;
|
||||||
|
|
||||||
// Any changes to the database format *must* include update-in-place code.
|
// Any changes to the database format *must* include update-in-place code.
|
||||||
// Original version: 2
|
// Original version: 2
|
||||||
|
@ -525,7 +527,8 @@ public final class DBHelper {
|
||||||
+ AccountColumns.POLICY_KEY + " integer, "
|
+ AccountColumns.POLICY_KEY + " integer, "
|
||||||
+ AccountColumns.MAX_ATTACHMENT_SIZE + " integer, "
|
+ AccountColumns.MAX_ATTACHMENT_SIZE + " integer, "
|
||||||
+ AccountColumns.PING_DURATION + " integer, "
|
+ AccountColumns.PING_DURATION + " integer, "
|
||||||
+ AccountColumns.AUTO_FETCH_ATTACHMENTS + " integer"
|
+ AccountColumns.AUTO_FETCH_ATTACHMENTS + " integer, "
|
||||||
|
+ AccountColumns.CAPABILITIES + " integer default 0"
|
||||||
+ ");";
|
+ ");";
|
||||||
db.execSQL("create table " + Account.TABLE_NAME + s);
|
db.execSQL("create table " + Account.TABLE_NAME + s);
|
||||||
// Deleting an account deletes associated Mailboxes and HostAuth's
|
// Deleting an account deletes associated Mailboxes and HostAuth's
|
||||||
|
@ -1562,6 +1565,52 @@ public final class DBHelper {
|
||||||
+ HostAuth.TABLE_NAME + "." + HostAuthColumns.PROTOCOL + "='imap'));");
|
+ HostAuth.TABLE_NAME + "." + HostAuthColumns.PROTOCOL + "='imap'));");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldVersion <= 130) {
|
||||||
|
//Account capabilities (check EmailServiceProxy#CAPABILITY_*)
|
||||||
|
try {
|
||||||
|
// Create capabilities field
|
||||||
|
db.execSQL("alter table " + Account.TABLE_NAME
|
||||||
|
+ " add column " + AccountColumns.CAPABILITIES
|
||||||
|
+ " integer" + " default 0;");
|
||||||
|
|
||||||
|
// Update all accounts with the appropriate capabilities
|
||||||
|
Cursor c = db.rawQuery("select " + Account.TABLE_NAME + "."
|
||||||
|
+ AccountColumns._ID + ", " + HostAuth.TABLE_NAME + "."
|
||||||
|
+ HostAuthColumns.PROTOCOL + " from " + Account.TABLE_NAME + ", "
|
||||||
|
+ HostAuth.TABLE_NAME + " where " + Account.TABLE_NAME + "."
|
||||||
|
+ AccountColumns.HOST_AUTH_KEY_RECV + " = " + HostAuth.TABLE_NAME
|
||||||
|
+ "." + HostAuthColumns._ID + ";", null);
|
||||||
|
if (c != null) {
|
||||||
|
try {
|
||||||
|
while(c.moveToNext()) {
|
||||||
|
long id = c.getLong(c.getColumnIndexOrThrow(AccountColumns._ID));
|
||||||
|
String protocol = c.getString(c.getColumnIndexOrThrow(
|
||||||
|
HostAuthColumns.PROTOCOL));
|
||||||
|
|
||||||
|
int capabilities = 0;
|
||||||
|
if (protocol.equals(LEGACY_SCHEME_IMAP)
|
||||||
|
|| protocol.equals(LEGACY_SCHEME_EAS)) {
|
||||||
|
// Don't know yet if the imap server supports the IDLE
|
||||||
|
// capability, but since this is upgrading the account,
|
||||||
|
// just assume that all imap servers supports the push
|
||||||
|
// capability and let disable it by the IMAP service
|
||||||
|
capabilities |= EmailServiceProxy.CAPABILITY_PUSH;
|
||||||
|
}
|
||||||
|
final ContentValues cv = new ContentValues(1);
|
||||||
|
cv.put(AccountColumns.CAPABILITIES, capabilities);
|
||||||
|
db.update(Account.TABLE_NAME, cv, AccountColumns._ID + " = ?",
|
||||||
|
new String[]{String.valueOf(id)});
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
c.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (final SQLException e) {
|
||||||
|
// Shouldn't be needed unless we're debugging and interrupt the process
|
||||||
|
LogUtils.w(TAG, "Exception upgrading EmailProvider.db from v129 to v130", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Due to a bug in commit 44a064e5f16ddaac25f2acfc03c118f65bc48aec,
|
// Due to a bug in commit 44a064e5f16ddaac25f2acfc03c118f65bc48aec,
|
||||||
// AUTO_FETCH_ATTACHMENTS column could not be available in the Account table.
|
// AUTO_FETCH_ATTACHMENTS column could not be available in the Account table.
|
||||||
// Since cm12 and up doesn't use this column, we are leave as is it. In case
|
// Since cm12 and up doesn't use this column, we are leave as is it. In case
|
||||||
|
|
|
@ -189,11 +189,11 @@ public class EmailProvider extends ContentProvider
|
||||||
"vnd.android.cursor.item/email-attachment";
|
"vnd.android.cursor.item/email-attachment";
|
||||||
|
|
||||||
/** Appended to the notification URI for delete operations */
|
/** Appended to the notification URI for delete operations */
|
||||||
private static final String NOTIFICATION_OP_DELETE = "delete";
|
public static final String NOTIFICATION_OP_DELETE = "delete";
|
||||||
/** Appended to the notification URI for insert operations */
|
/** Appended to the notification URI for insert operations */
|
||||||
private static final String NOTIFICATION_OP_INSERT = "insert";
|
public static final String NOTIFICATION_OP_INSERT = "insert";
|
||||||
/** Appended to the notification URI for update operations */
|
/** Appended to the notification URI for update operations */
|
||||||
private static final String NOTIFICATION_OP_UPDATE = "update";
|
public static final String NOTIFICATION_OP_UPDATE = "update";
|
||||||
|
|
||||||
/** The query string to trigger a folder refresh. */
|
/** The query string to trigger a folder refresh. */
|
||||||
protected static String QUERY_UIREFRESH = "uirefresh";
|
protected static String QUERY_UIREFRESH = "uirefresh";
|
||||||
|
@ -833,6 +833,7 @@ public class EmailProvider extends ContentProvider
|
||||||
|
|
||||||
// Notify all notifier cursors
|
// Notify all notifier cursors
|
||||||
sendNotifierChange(getBaseNotificationUri(match), NOTIFICATION_OP_DELETE, id);
|
sendNotifierChange(getBaseNotificationUri(match), NOTIFICATION_OP_DELETE, id);
|
||||||
|
sendSyncSettingChanged(getBaseSyncSettingChangedUri(match), NOTIFICATION_OP_DELETE, id);
|
||||||
|
|
||||||
// Notify all email content cursors
|
// Notify all email content cursors
|
||||||
notifyUI(EmailContent.CONTENT_URI, null);
|
notifyUI(EmailContent.CONTENT_URI, null);
|
||||||
|
@ -1075,6 +1076,7 @@ public class EmailProvider extends ContentProvider
|
||||||
|
|
||||||
// Notify all notifier cursors
|
// Notify all notifier cursors
|
||||||
sendNotifierChange(getBaseNotificationUri(match), NOTIFICATION_OP_INSERT, id);
|
sendNotifierChange(getBaseNotificationUri(match), NOTIFICATION_OP_INSERT, id);
|
||||||
|
sendSyncSettingChanged(getBaseSyncSettingChangedUri(match), NOTIFICATION_OP_INSERT, id);
|
||||||
|
|
||||||
// Notify all existing cursors.
|
// Notify all existing cursors.
|
||||||
notifyUI(EmailContent.CONTENT_URI, null);
|
notifyUI(EmailContent.CONTENT_URI, null);
|
||||||
|
@ -1924,7 +1926,7 @@ public class EmailProvider extends ContentProvider
|
||||||
private static final int INDEX_SYNC_KEY = 2;
|
private static final int INDEX_SYNC_KEY = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restart push if we need it (currently only for Exchange accounts).
|
* Restart push if we need it.
|
||||||
* @param context A {@link Context}.
|
* @param context A {@link Context}.
|
||||||
* @param db The {@link SQLiteDatabase}.
|
* @param db The {@link SQLiteDatabase}.
|
||||||
* @param id The id of the thing we're looking for.
|
* @param id The id of the thing we're looking for.
|
||||||
|
@ -1937,9 +1939,13 @@ public class EmailProvider extends ContentProvider
|
||||||
try {
|
try {
|
||||||
if (c.moveToFirst()) {
|
if (c.moveToFirst()) {
|
||||||
final String protocol = c.getString(INDEX_PROTOCOL);
|
final String protocol = c.getString(INDEX_PROTOCOL);
|
||||||
// Only restart push for EAS accounts that have completed initial sync.
|
final String syncKey = c.getString(INDEX_SYNC_KEY);
|
||||||
if (context.getString(R.string.protocol_eas).equals(protocol) &&
|
final boolean supportsPush =
|
||||||
!EmailContent.isInitialSyncKey(c.getString(INDEX_SYNC_KEY))) {
|
context.getString(R.string.protocol_eas).equals(protocol) ||
|
||||||
|
context.getString(R.string.protocol_legacy_imap).equals(protocol);
|
||||||
|
|
||||||
|
// Only restart push for EAS or IMAP accounts that have completed initial sync.
|
||||||
|
if (supportsPush && !EmailContent.isInitialSyncKey(syncKey)) {
|
||||||
final String emailAddress = c.getString(INDEX_EMAIL_ADDRESS);
|
final String emailAddress = c.getString(INDEX_EMAIL_ADDRESS);
|
||||||
final android.accounts.Account account =
|
final android.accounts.Account account =
|
||||||
getAccountManagerAccount(context, emailAddress, protocol);
|
getAccountManagerAccount(context, emailAddress, protocol);
|
||||||
|
@ -2010,6 +2016,7 @@ public class EmailProvider extends ContentProvider
|
||||||
final SQLiteDatabase db = getDatabase(context);
|
final SQLiteDatabase db = getDatabase(context);
|
||||||
final int table = match >> BASE_SHIFT;
|
final int table = match >> BASE_SHIFT;
|
||||||
int result;
|
int result;
|
||||||
|
boolean syncSettingChanged = false;
|
||||||
|
|
||||||
// We do NOT allow setting of unreadCount/messageCount via the provider
|
// We do NOT allow setting of unreadCount/messageCount via the provider
|
||||||
// These columns are maintained via triggers
|
// These columns are maintained via triggers
|
||||||
|
@ -2159,6 +2166,14 @@ public class EmailProvider extends ContentProvider
|
||||||
}
|
}
|
||||||
} else if (match == MESSAGE_ID) {
|
} else if (match == MESSAGE_ID) {
|
||||||
db.execSQL(UPDATED_MESSAGE_DELETE + id);
|
db.execSQL(UPDATED_MESSAGE_DELETE + id);
|
||||||
|
} else if (match == MAILBOX_ID) {
|
||||||
|
if (values.containsKey(MailboxColumns.SYNC_INTERVAL)) {
|
||||||
|
syncSettingChanged = true;
|
||||||
|
}
|
||||||
|
} else if (match == ACCOUNT_ID) {
|
||||||
|
if (values.containsKey(AccountColumns.SYNC_INTERVAL)) {
|
||||||
|
syncSettingChanged = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
result = db.update(tableName, values, whereWithId(id, selection),
|
result = db.update(tableName, values, whereWithId(id, selection),
|
||||||
selectionArgs);
|
selectionArgs);
|
||||||
|
@ -2293,6 +2308,10 @@ public class EmailProvider extends ContentProvider
|
||||||
TextUtils.isEmpty(values.getAsString(AttachmentColumns.LOCATION))) {
|
TextUtils.isEmpty(values.getAsString(AttachmentColumns.LOCATION))) {
|
||||||
LogUtils.w(TAG, new Throwable(), "attachment with blank location");
|
LogUtils.w(TAG, new Throwable(), "attachment with blank location");
|
||||||
}
|
}
|
||||||
|
} else if (match == MAILBOX) {
|
||||||
|
if (values.containsKey(MailboxColumns.SYNC_INTERVAL)) {
|
||||||
|
syncSettingChanged = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
result = db.update(tableName, values, selection, selectionArgs);
|
result = db.update(tableName, values, selection, selectionArgs);
|
||||||
break;
|
break;
|
||||||
|
@ -2314,6 +2333,10 @@ public class EmailProvider extends ContentProvider
|
||||||
// Notify all notifier cursors if some records where changed in the database
|
// Notify all notifier cursors if some records where changed in the database
|
||||||
if (result > 0) {
|
if (result > 0) {
|
||||||
sendNotifierChange(getBaseNotificationUri(match), NOTIFICATION_OP_UPDATE, id);
|
sendNotifierChange(getBaseNotificationUri(match), NOTIFICATION_OP_UPDATE, id);
|
||||||
|
if (syncSettingChanged) {
|
||||||
|
sendSyncSettingChanged(getBaseSyncSettingChangedUri(match),
|
||||||
|
NOTIFICATION_OP_UPDATE, id);
|
||||||
|
}
|
||||||
notifyUI(notificationUri, null);
|
notifyUI(notificationUri, null);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -2544,6 +2567,21 @@ public class EmailProvider extends ContentProvider
|
||||||
return baseUri;
|
return baseUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Uri getBaseSyncSettingChangedUri(int match) {
|
||||||
|
Uri baseUri = null;
|
||||||
|
switch (match) {
|
||||||
|
case ACCOUNT:
|
||||||
|
case ACCOUNT_ID:
|
||||||
|
baseUri = Account.SYNC_SETTING_CHANGED_URI;
|
||||||
|
break;
|
||||||
|
case MAILBOX:
|
||||||
|
case MAILBOX_ID:
|
||||||
|
baseUri = Mailbox.SYNC_SETTING_CHANGED_URI;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return baseUri;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a change notification to any cursors observers of the given base URI. The final
|
* Sends a change notification to any cursors observers of the given base URI. The final
|
||||||
* notification URI is dynamically built to contain the specified information. It will be
|
* notification URI is dynamically built to contain the specified information. It will be
|
||||||
|
@ -2582,6 +2620,25 @@ public class EmailProvider extends ContentProvider
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void sendSyncSettingChanged(Uri baseUri, String op, String id) {
|
||||||
|
if (baseUri == null) return;
|
||||||
|
|
||||||
|
// Append the operation, if specified
|
||||||
|
if (op != null) {
|
||||||
|
baseUri = baseUri.buildUpon().appendEncodedPath(op).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
long longId = 0L;
|
||||||
|
try {
|
||||||
|
longId = Long.valueOf(id);
|
||||||
|
} catch (NumberFormatException ignore) {}
|
||||||
|
if (longId > 0) {
|
||||||
|
notifyUI(baseUri, id);
|
||||||
|
} else {
|
||||||
|
notifyUI(baseUri, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void sendMessageListDataChangedNotification() {
|
private void sendMessageListDataChangedNotification() {
|
||||||
final Context context = getContext();
|
final Context context = getContext();
|
||||||
final Intent intent = new Intent(ACTION_NOTIFY_MESSAGE_LIST_DATASET_CHANGED);
|
final Intent intent = new Intent(ACTION_NOTIFY_MESSAGE_LIST_DATASET_CHANGED);
|
||||||
|
|
|
@ -40,6 +40,7 @@ import com.android.emailcommon.utility.ConversionUtilities;
|
||||||
import com.android.mail.utils.LogUtils;
|
import com.android.mail.utils.LogUtils;
|
||||||
import com.android.mail.utils.Utils;
|
import com.android.mail.utils.Utils;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
@ -118,8 +119,9 @@ public class Utilities {
|
||||||
ArrayList<Part> attachments = new ArrayList<Part>();
|
ArrayList<Part> attachments = new ArrayList<Part>();
|
||||||
MimeUtility.collectParts(message, viewables, attachments);
|
MimeUtility.collectParts(message, viewables, attachments);
|
||||||
|
|
||||||
|
// Don't close the viewables attachment InputStream yet
|
||||||
final ConversionUtilities.BodyFieldData data =
|
final ConversionUtilities.BodyFieldData data =
|
||||||
ConversionUtilities.parseBodyFields(viewables);
|
ConversionUtilities.parseBodyFields(viewables, false);
|
||||||
|
|
||||||
// set body and local message values
|
// set body and local message values
|
||||||
localMessage.setFlags(data.isQuotedReply, data.isQuotedForward);
|
localMessage.setFlags(data.isQuotedReply, data.isQuotedForward);
|
||||||
|
@ -166,6 +168,21 @@ public class Utilities {
|
||||||
localMessage.mFlagAttachment = true;
|
localMessage.mFlagAttachment = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Close any parts that may still be open
|
||||||
|
for (final Part part : viewables) {
|
||||||
|
if (part.getBody() == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
InputStream is = part.getBody().getInputStream();
|
||||||
|
if (is != null) {
|
||||||
|
is.close();
|
||||||
|
}
|
||||||
|
} catch (IOException io) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// One last update of message with two updated flags
|
// One last update of message with two updated flags
|
||||||
localMessage.mFlagLoaded = loadStatus;
|
localMessage.mFlagLoaded = loadStatus;
|
||||||
|
|
||||||
|
|
|
@ -293,6 +293,10 @@ public class EmailBroadcastProcessorService extends IntentService {
|
||||||
private void onBootCompleted() {
|
private void onBootCompleted() {
|
||||||
performOneTimeInitialization();
|
performOneTimeInitialization();
|
||||||
reconcileAndStartServices();
|
reconcileAndStartServices();
|
||||||
|
|
||||||
|
// This is an special case to start IMAP PUSH via its adapter
|
||||||
|
Intent imap = new Intent(this, LegacyImapSyncAdapterService.class);
|
||||||
|
startService(imap);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void reconcileAndStartServices() {
|
private void reconcileAndStartServices() {
|
||||||
|
|
|
@ -288,6 +288,12 @@ public abstract class EmailServiceStub extends IEmailService.Stub implements IEm
|
||||||
mailbox = Mailbox.restoreMailboxWithId(mContext, message.mMainMailboxKey);
|
mailbox = Mailbox.restoreMailboxWithId(mContext, message.mMainMailboxKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message.mServerId == null) {
|
||||||
|
cb.loadAttachmentStatus(messageId, attachmentId,
|
||||||
|
EmailServiceStatus.MESSAGE_NOT_FOUND, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (account == null || mailbox == null) {
|
if (account == null || mailbox == null) {
|
||||||
// If the account/mailbox are gone, just report success; the UI handles this
|
// If the account/mailbox are gone, just report success; the UI handles this
|
||||||
cb.loadAttachmentStatus(messageId, attachmentId,
|
cb.loadAttachmentStatus(messageId, attachmentId,
|
||||||
|
@ -416,7 +422,6 @@ public abstract class EmailServiceStub extends IEmailService.Stub implements IEm
|
||||||
// actually occurs.
|
// actually occurs.
|
||||||
mailbox.mUiSyncStatus = Mailbox.SYNC_STATUS_INITIAL_SYNC_NEEDED;
|
mailbox.mUiSyncStatus = Mailbox.SYNC_STATUS_INITIAL_SYNC_NEEDED;
|
||||||
}
|
}
|
||||||
mailbox.save(mContext);
|
|
||||||
if (type == Mailbox.TYPE_INBOX) {
|
if (type == Mailbox.TYPE_INBOX) {
|
||||||
inboxId = mailbox.mId;
|
inboxId = mailbox.mId;
|
||||||
|
|
||||||
|
@ -425,6 +430,7 @@ public abstract class EmailServiceStub extends IEmailService.Stub implements IEm
|
||||||
// should start marked
|
// should start marked
|
||||||
mailbox.mSyncInterval = 1;
|
mailbox.mSyncInterval = 1;
|
||||||
}
|
}
|
||||||
|
mailbox.save(mContext);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -16,5 +16,139 @@
|
||||||
|
|
||||||
package com.android.email.service;
|
package com.android.email.service;
|
||||||
|
|
||||||
|
import static com.android.emailcommon.Logging.LOG_TAG;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import android.content.AbstractThreadedSyncAdapter;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.ContentProviderClient;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
|
import android.content.SyncResult;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.os.PowerManager;
|
||||||
|
import android.text.format.DateUtils;
|
||||||
|
|
||||||
|
import com.android.emailcommon.Logging;
|
||||||
|
import com.android.emailcommon.provider.Mailbox;
|
||||||
|
import com.android.emailcommon.service.IEmailService;
|
||||||
|
import com.android.mail.utils.LogUtils;
|
||||||
|
|
||||||
public class LegacyImapSyncAdapterService extends PopImapSyncAdapterService {
|
public class LegacyImapSyncAdapterService extends PopImapSyncAdapterService {
|
||||||
}
|
|
||||||
|
// The call to ServiceConnection.onServiceConnected is asynchronous to bindService. It's
|
||||||
|
// possible for that to be delayed if, in which case, a call to onPerformSync
|
||||||
|
// could occur before we have a connection to the service.
|
||||||
|
// In onPerformSync, if we don't yet have our ImapService, we will wait for up to 10
|
||||||
|
// seconds for it to appear. If it takes longer than that, we will fail the sync.
|
||||||
|
private static final long MAX_WAIT_FOR_SERVICE_MS = 10 * DateUtils.SECOND_IN_MILLIS;
|
||||||
|
|
||||||
|
private static final ExecutorService sExecutor = Executors.newCachedThreadPool();
|
||||||
|
|
||||||
|
private IEmailService mImapService;
|
||||||
|
|
||||||
|
private final ServiceConnection mConnection = new ServiceConnection() {
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName name, IBinder binder) {
|
||||||
|
if (Logging.LOGD) {
|
||||||
|
LogUtils.v(LOG_TAG, "onServiceConnected");
|
||||||
|
}
|
||||||
|
synchronized (mConnection) {
|
||||||
|
mImapService = IEmailService.Stub.asInterface(binder);
|
||||||
|
mConnection.notify();
|
||||||
|
|
||||||
|
// We need to run this task in the background (not in UI-Thread)
|
||||||
|
sExecutor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
final Context context = LegacyImapSyncAdapterService.this;
|
||||||
|
ImapService.registerAllImapIdleMailboxes(context, mImapService);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName name) {
|
||||||
|
mImapService = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected class ImapSyncAdapterImpl extends SyncAdapterImpl {
|
||||||
|
public ImapSyncAdapterImpl(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPerformSync(android.accounts.Account account, Bundle extras,
|
||||||
|
String authority, ContentProviderClient provider, SyncResult syncResult) {
|
||||||
|
|
||||||
|
final Context context = LegacyImapSyncAdapterService.this;
|
||||||
|
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||||
|
PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
|
||||||
|
"Imap Sync WakeLock");
|
||||||
|
try {
|
||||||
|
wl.acquire();
|
||||||
|
|
||||||
|
if (!waitForService()) {
|
||||||
|
// The service didn't connect, nothing we can do.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Mailbox.isPushOnlyExtras(extras)) {
|
||||||
|
super.onPerformSync(account, extras, authority, provider, syncResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if IMAP push service is necessary
|
||||||
|
ImapService.stopImapPushServiceIfNecessary(context);
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
wl.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractThreadedSyncAdapter getSyncAdapter() {
|
||||||
|
return new ImapSyncAdapterImpl(getApplicationContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
bindService(new Intent(this, ImapService.class), mConnection, Context.BIND_AUTO_CREATE);
|
||||||
|
startService(new Intent(this, LegacyImapSyncAdapterService.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
unbindService(mConnection);
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final boolean waitForService() {
|
||||||
|
synchronized(mConnection) {
|
||||||
|
if (mImapService == null) {
|
||||||
|
if (Logging.LOGD) {
|
||||||
|
LogUtils.v(LOG_TAG, "ImapService not yet connected");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
mConnection.wait(MAX_WAIT_FOR_SERVICE_MS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
LogUtils.wtf(LOG_TAG, "InterrupedException waiting for ImapService to connect");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (mImapService == null) {
|
||||||
|
LogUtils.wtf(LOG_TAG, "timed out waiting for ImapService to connect");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ import java.util.ArrayList;
|
||||||
|
|
||||||
public class PopImapSyncAdapterService extends Service {
|
public class PopImapSyncAdapterService extends Service {
|
||||||
private static final String TAG = "PopImapSyncService";
|
private static final String TAG = "PopImapSyncService";
|
||||||
private SyncAdapterImpl mSyncAdapter = null;
|
private AbstractThreadedSyncAdapter mSyncAdapter = null;
|
||||||
|
|
||||||
private static String sPop3Protocol;
|
private static String sPop3Protocol;
|
||||||
private static String sLegacyImapProtocol;
|
private static String sLegacyImapProtocol;
|
||||||
|
@ -58,7 +58,7 @@ public class PopImapSyncAdapterService extends Service {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class SyncAdapterImpl extends AbstractThreadedSyncAdapter {
|
static class SyncAdapterImpl extends AbstractThreadedSyncAdapter {
|
||||||
public SyncAdapterImpl(Context context) {
|
public SyncAdapterImpl(Context context) {
|
||||||
super(context, true /* autoInitialize */);
|
super(context, true /* autoInitialize */);
|
||||||
}
|
}
|
||||||
|
@ -71,10 +71,14 @@ public class PopImapSyncAdapterService extends Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AbstractThreadedSyncAdapter getSyncAdapter() {
|
||||||
|
return new SyncAdapterImpl(getApplicationContext());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
mSyncAdapter = new SyncAdapterImpl(getApplicationContext());
|
mSyncAdapter = getSyncAdapter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -101,14 +105,14 @@ public class PopImapSyncAdapterService extends Service {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void sync(final Context context, final long mailboxId,
|
private static boolean sync(final Context context, final long mailboxId,
|
||||||
final Bundle extras, final SyncResult syncResult, final boolean uiRefresh,
|
final Bundle extras, final SyncResult syncResult, final boolean uiRefresh,
|
||||||
final int deltaMessageCount) {
|
final int deltaMessageCount) {
|
||||||
TempDirectory.setTempDirectory(context);
|
TempDirectory.setTempDirectory(context);
|
||||||
Mailbox mailbox = Mailbox.restoreMailboxWithId(context, mailboxId);
|
Mailbox mailbox = Mailbox.restoreMailboxWithId(context, mailboxId);
|
||||||
if (mailbox == null) return;
|
if (mailbox == null) return false;
|
||||||
Account account = Account.restoreAccountWithId(context, mailbox.mAccountKey);
|
Account account = Account.restoreAccountWithId(context, mailbox.mAccountKey);
|
||||||
if (account == null) return;
|
if (account == null) return false;
|
||||||
ContentResolver resolver = context.getContentResolver();
|
ContentResolver resolver = context.getContentResolver();
|
||||||
if ((mailbox.mType != Mailbox.TYPE_OUTBOX) &&
|
if ((mailbox.mType != Mailbox.TYPE_OUTBOX) &&
|
||||||
!loadsFromServer(context, mailbox, account)) {
|
!loadsFromServer(context, mailbox, account)) {
|
||||||
|
@ -116,7 +120,7 @@ public class PopImapSyncAdapterService extends Service {
|
||||||
// updates table and return
|
// updates table and return
|
||||||
resolver.delete(Message.UPDATED_CONTENT_URI, MessageColumns.MAILBOX_KEY + "=?",
|
resolver.delete(Message.UPDATED_CONTENT_URI, MessageColumns.MAILBOX_KEY + "=?",
|
||||||
new String[] {Long.toString(mailbox.mId)});
|
new String[] {Long.toString(mailbox.mId)});
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
LogUtils.d(TAG, "About to sync mailbox: " + mailbox.mDisplayName);
|
LogUtils.d(TAG, "About to sync mailbox: " + mailbox.mDisplayName);
|
||||||
|
|
||||||
|
@ -147,6 +151,7 @@ public class PopImapSyncAdapterService extends Service {
|
||||||
}
|
}
|
||||||
EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, status, 0,
|
EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, status, 0,
|
||||||
lastSyncResult);
|
lastSyncResult);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
} catch (MessagingException e) {
|
} catch (MessagingException e) {
|
||||||
final int type = e.getExceptionType();
|
final int type = e.getExceptionType();
|
||||||
|
@ -186,6 +191,7 @@ public class PopImapSyncAdapterService extends Service {
|
||||||
values.put(Mailbox.SYNC_TIME, System.currentTimeMillis());
|
values.put(Mailbox.SYNC_TIME, System.currentTimeMillis());
|
||||||
resolver.update(mailboxUri, values, null, null);
|
resolver.update(mailboxUri, values, null, null);
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -247,7 +253,8 @@ public class PopImapSyncAdapterService extends Service {
|
||||||
// from the account settings. Otherwise just sync the inbox.
|
// from the account settings. Otherwise just sync the inbox.
|
||||||
if (info.offerLookback) {
|
if (info.offerLookback) {
|
||||||
mailboxIds = getLoopBackMailboxIdsForSync(context, acct);
|
mailboxIds = getLoopBackMailboxIdsForSync(context, acct);
|
||||||
} else {
|
}
|
||||||
|
if (mailboxIds.length == 0) {
|
||||||
final long inboxId = Mailbox.findMailboxOfType(context, acct.mId,
|
final long inboxId = Mailbox.findMailboxOfType(context, acct.mId,
|
||||||
Mailbox.TYPE_INBOX);
|
Mailbox.TYPE_INBOX);
|
||||||
if (inboxId != Mailbox.NO_MAILBOX) {
|
if (inboxId != Mailbox.NO_MAILBOX) {
|
||||||
|
@ -262,9 +269,20 @@ public class PopImapSyncAdapterService extends Service {
|
||||||
extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
|
extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
|
||||||
int deltaMessageCount =
|
int deltaMessageCount =
|
||||||
extras.getInt(Mailbox.SYNC_EXTRA_DELTA_MESSAGE_COUNT, 0);
|
extras.getInt(Mailbox.SYNC_EXTRA_DELTA_MESSAGE_COUNT, 0);
|
||||||
|
boolean success = mailboxIds.length > 0;
|
||||||
for (long mailboxId : mailboxIds) {
|
for (long mailboxId : mailboxIds) {
|
||||||
sync(context, mailboxId, extras, syncResult, uiRefresh,
|
boolean result = sync(context, mailboxId, extras, syncResult,
|
||||||
deltaMessageCount);
|
uiRefresh, deltaMessageCount);
|
||||||
|
if (!result) {
|
||||||
|
success = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial sync performed?
|
||||||
|
if (success) {
|
||||||
|
// All mailboxes (that need a sync) are now synced. Assume we
|
||||||
|
// have a valid sync key, in case this account has push support
|
||||||
|
markAsInitialSyncKey(context, acct.mId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -278,6 +296,14 @@ public class PopImapSyncAdapterService extends Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void markAsInitialSyncKey(Context context, long accountId) {
|
||||||
|
ContentResolver resolver = context.getContentResolver();
|
||||||
|
Uri accountUri = ContentUris.withAppendedId(Account.CONTENT_URI, accountId);
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(AccountColumns.SYNC_KEY, "1");
|
||||||
|
resolver.update(accountUri, values, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean isLegacyImapProtocol(Context ctx, Account acct) {
|
private static boolean isLegacyImapProtocol(Context ctx, Account acct) {
|
||||||
if (sLegacyImapProtocol == null) {
|
if (sLegacyImapProtocol == null) {
|
||||||
sLegacyImapProtocol = ctx.getString(R.string.protocol_legacy_imap);
|
sLegacyImapProtocol = ctx.getString(R.string.protocol_legacy_imap);
|
||||||
|
|
|
@ -74,8 +74,8 @@
|
||||||
email:serviceClass="com.android.email.service.ImapService"
|
email:serviceClass="com.android.email.service.ImapService"
|
||||||
email:port="143"
|
email:port="143"
|
||||||
email:portSsl="993"
|
email:portSsl="993"
|
||||||
email:syncIntervalStrings="@array/account_settings_check_frequency_entries"
|
email:syncIntervalStrings="@array/account_settings_check_frequency_entries_push"
|
||||||
email:syncIntervals="@array/account_settings_check_frequency_values"
|
email:syncIntervals="@array/account_settings_check_frequency_values_push"
|
||||||
email:defaultSyncInterval="mins15"
|
email:defaultSyncInterval="mins15"
|
||||||
|
|
||||||
email:offerTls="true"
|
email:offerTls="true"
|
||||||
|
|
|
@ -23,5 +23,6 @@
|
||||||
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
|
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:contentAuthority="@string/authority_email_provider"
|
android:contentAuthority="@string/authority_email_provider"
|
||||||
android:accountType="@string/account_manager_type_legacy_imap"
|
android:accountType="@string/account_manager_type_legacy_imap"
|
||||||
|
android:allowParallelSyncs="true"
|
||||||
android:supportsUploading="true"
|
android:supportsUploading="true"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -417,6 +417,10 @@ public class AccountCheckSettingsFragment extends Fragment {
|
||||||
EmailServiceProxy.VALIDATE_BUNDLE_ERROR_MESSAGE);
|
EmailServiceProxy.VALIDATE_BUNDLE_ERROR_MESSAGE);
|
||||||
return new MessagingException(resultCode, errorMessage);
|
return new MessagingException(resultCode, errorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save account capabilities
|
||||||
|
mAccount.mCapabilities = bundle.getInt(
|
||||||
|
EmailServiceProxy.SETTINGS_BUNDLE_CAPABILITIES, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
final EmailServiceInfo info;
|
final EmailServiceInfo info;
|
||||||
|
|
|
@ -69,6 +69,7 @@ import com.android.emailcommon.provider.EmailContent;
|
||||||
import com.android.emailcommon.provider.EmailContent.AccountColumns;
|
import com.android.emailcommon.provider.EmailContent.AccountColumns;
|
||||||
import com.android.emailcommon.provider.Mailbox;
|
import com.android.emailcommon.provider.Mailbox;
|
||||||
import com.android.emailcommon.provider.Policy;
|
import com.android.emailcommon.provider.Policy;
|
||||||
|
import com.android.emailcommon.service.EmailServiceProxy;
|
||||||
import com.android.mail.preferences.AccountPreferences;
|
import com.android.mail.preferences.AccountPreferences;
|
||||||
import com.android.mail.preferences.FolderPreferences;
|
import com.android.mail.preferences.FolderPreferences;
|
||||||
import com.android.mail.preferences.FolderPreferences.NotificationLight;
|
import com.android.mail.preferences.FolderPreferences.NotificationLight;
|
||||||
|
@ -84,7 +85,9 @@ import com.android.mail.utils.LogUtils;
|
||||||
import com.android.mail.utils.NotificationUtils;
|
import com.android.mail.utils.NotificationUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -243,10 +246,7 @@ public class AccountSettingsFragment extends MailAccountPrefsFragment
|
||||||
final CharSequence [] syncIntervals =
|
final CharSequence [] syncIntervals =
|
||||||
savedInstanceState.getCharSequenceArray(SAVESTATE_SYNC_INTERVALS);
|
savedInstanceState.getCharSequenceArray(SAVESTATE_SYNC_INTERVALS);
|
||||||
mCheckFrequency = (ListPreference) findPreference(PREFERENCE_FREQUENCY);
|
mCheckFrequency = (ListPreference) findPreference(PREFERENCE_FREQUENCY);
|
||||||
if (mCheckFrequency != null) {
|
fillCheckFrecuency(syncIntervalStrings, syncIntervals);
|
||||||
mCheckFrequency.setEntries(syncIntervalStrings);
|
|
||||||
mCheckFrequency.setEntryValues(syncIntervals);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,16 +382,15 @@ public class AccountSettingsFragment extends MailAccountPrefsFragment
|
||||||
final android.accounts.Account androidAcct = new android.accounts.Account(
|
final android.accounts.Account androidAcct = new android.accounts.Account(
|
||||||
mAccount.mEmailAddress, mServiceInfo.accountType);
|
mAccount.mEmailAddress, mServiceInfo.accountType);
|
||||||
if (Integer.parseInt(summary) == Account.CHECK_INTERVAL_NEVER) {
|
if (Integer.parseInt(summary) == Account.CHECK_INTERVAL_NEVER) {
|
||||||
// Disable syncing from the account manager. Leave the current sync frequency
|
// Disable syncing from the account manager.
|
||||||
// in the database.
|
|
||||||
ContentResolver.setSyncAutomatically(androidAcct, EmailContent.AUTHORITY,
|
ContentResolver.setSyncAutomatically(androidAcct, EmailContent.AUTHORITY,
|
||||||
false);
|
false);
|
||||||
} else {
|
} else {
|
||||||
// Enable syncing from the account manager.
|
// Enable syncing from the account manager.
|
||||||
ContentResolver.setSyncAutomatically(androidAcct, EmailContent.AUTHORITY,
|
ContentResolver.setSyncAutomatically(androidAcct, EmailContent.AUTHORITY,
|
||||||
true);
|
true);
|
||||||
cv.put(AccountColumns.SYNC_INTERVAL, Integer.parseInt(summary));
|
|
||||||
}
|
}
|
||||||
|
cv.put(AccountColumns.SYNC_INTERVAL, Integer.parseInt(summary));
|
||||||
}
|
}
|
||||||
} else if (key.equals(PREFERENCE_SYNC_WINDOW)) {
|
} else if (key.equals(PREFERENCE_SYNC_WINDOW)) {
|
||||||
final String summary = newValue.toString();
|
final String summary = newValue.toString();
|
||||||
|
@ -749,8 +748,7 @@ public class AccountSettingsFragment extends MailAccountPrefsFragment
|
||||||
R.string.preferences_signature_summary_not_set);
|
R.string.preferences_signature_summary_not_set);
|
||||||
|
|
||||||
mCheckFrequency = (ListPreference) findPreference(PREFERENCE_FREQUENCY);
|
mCheckFrequency = (ListPreference) findPreference(PREFERENCE_FREQUENCY);
|
||||||
mCheckFrequency.setEntries(mServiceInfo.syncIntervalStrings);
|
fillCheckFrecuency(mServiceInfo.syncIntervalStrings, mServiceInfo.syncIntervals);
|
||||||
mCheckFrequency.setEntryValues(mServiceInfo.syncIntervals);
|
|
||||||
if (mServiceInfo.syncContacts || mServiceInfo.syncCalendar) {
|
if (mServiceInfo.syncContacts || mServiceInfo.syncCalendar) {
|
||||||
// This account allows syncing of contacts and/or calendar, so we will always have
|
// This account allows syncing of contacts and/or calendar, so we will always have
|
||||||
// separate preferences to enable or disable syncing of email, contacts, and calendar.
|
// separate preferences to enable or disable syncing of email, contacts, and calendar.
|
||||||
|
@ -1182,4 +1180,28 @@ public class AccountSettingsFragment extends MailAccountPrefsFragment
|
||||||
}
|
}
|
||||||
mInboxLights.setOn(notificationLight.mOn);
|
mInboxLights.setOn(notificationLight.mOn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void fillCheckFrecuency(CharSequence[] labels, CharSequence[] values) {
|
||||||
|
if (mCheckFrequency == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check push capability prior to include as an option
|
||||||
|
if (mAccount != null) {
|
||||||
|
boolean hasPushCapability = mAccount.hasCapability(EmailServiceProxy.CAPABILITY_PUSH);
|
||||||
|
List<CharSequence> valuesList = new ArrayList<>(Arrays.asList(values));
|
||||||
|
int checkIntervalPushPos = valuesList.indexOf(
|
||||||
|
String.valueOf(Account.CHECK_INTERVAL_PUSH));
|
||||||
|
if (!hasPushCapability && checkIntervalPushPos != -1) {
|
||||||
|
List<CharSequence> labelsList = new ArrayList<>(Arrays.asList(labels));
|
||||||
|
labelsList.remove(checkIntervalPushPos);
|
||||||
|
valuesList.remove(checkIntervalPushPos);
|
||||||
|
labels = labelsList.toArray(new CharSequence[labelsList.size()]);
|
||||||
|
values = valuesList.toArray(new CharSequence[valuesList.size()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mCheckFrequency.setEntries(labels);
|
||||||
|
mCheckFrequency.setEntryValues(values);
|
||||||
|
mCheckFrequency.setDefaultValue(values);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -917,7 +917,7 @@ public class AccountSetupFinal extends AccountSetupActivity
|
||||||
public void setDefaultsForProtocol(Account account) {
|
public void setDefaultsForProtocol(Account account) {
|
||||||
final EmailServiceUtils.EmailServiceInfo info = mSetupData.getIncomingServiceInfo(this);
|
final EmailServiceUtils.EmailServiceInfo info = mSetupData.getIncomingServiceInfo(this);
|
||||||
if (info == null) return;
|
if (info == null) return;
|
||||||
account.mSyncInterval = info.defaultSyncInterval;
|
account.setSyncInterval(info.defaultSyncInterval);
|
||||||
account.mSyncLookback = info.defaultLookback;
|
account.mSyncLookback = info.defaultLookback;
|
||||||
if (info.offerLocalDeletes) {
|
if (info.offerLocalDeletes) {
|
||||||
account.setDeletePolicy(info.defaultLocalDeletes);
|
account.setDeletePolicy(info.defaultLocalDeletes);
|
||||||
|
|
|
@ -29,8 +29,13 @@ import com.android.email.activity.UiUtilities;
|
||||||
import com.android.email.service.EmailServiceUtils;
|
import com.android.email.service.EmailServiceUtils;
|
||||||
import com.android.emailcommon.provider.Account;
|
import com.android.emailcommon.provider.Account;
|
||||||
import com.android.emailcommon.provider.Policy;
|
import com.android.emailcommon.provider.Policy;
|
||||||
|
import com.android.emailcommon.service.EmailServiceProxy;
|
||||||
import com.android.emailcommon.service.SyncWindow;
|
import com.android.emailcommon.service.SyncWindow;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public class AccountSetupOptionsFragment extends AccountSetupFragment {
|
public class AccountSetupOptionsFragment extends AccountSetupFragment {
|
||||||
private Spinner mCheckFrequencyView;
|
private Spinner mCheckFrequencyView;
|
||||||
private Spinner mSyncWindowView;
|
private Spinner mSyncWindowView;
|
||||||
|
@ -90,11 +95,24 @@ public class AccountSetupOptionsFragment extends AccountSetupFragment {
|
||||||
final CharSequence[] frequencyEntries = serviceInfo.syncIntervalStrings;
|
final CharSequence[] frequencyEntries = serviceInfo.syncIntervalStrings;
|
||||||
|
|
||||||
// Now create the array used by the sync interval Spinner
|
// Now create the array used by the sync interval Spinner
|
||||||
final SpinnerOption[] checkFrequencies = new SpinnerOption[frequencyEntries.length];
|
int checkIntervalPushPos = -1;
|
||||||
|
SpinnerOption[] checkFrequencies = new SpinnerOption[frequencyEntries.length];
|
||||||
for (int i = 0; i < frequencyEntries.length; i++) {
|
for (int i = 0; i < frequencyEntries.length; i++) {
|
||||||
checkFrequencies[i] = new SpinnerOption(
|
Integer value = Integer.valueOf(frequencyValues[i].toString());
|
||||||
Integer.valueOf(frequencyValues[i].toString()), frequencyEntries[i].toString());
|
if (value.intValue() == Account.CHECK_INTERVAL_PUSH) {
|
||||||
|
checkIntervalPushPos = i;
|
||||||
|
}
|
||||||
|
checkFrequencies[i] = new SpinnerOption(value, frequencyEntries[i].toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure that push capability is supported by the server
|
||||||
|
boolean hasPushCapability = account.hasCapability(EmailServiceProxy.CAPABILITY_PUSH);
|
||||||
|
if (!hasPushCapability && checkIntervalPushPos != -1) {
|
||||||
|
List<SpinnerOption> options = new ArrayList<>(Arrays.asList(checkFrequencies));
|
||||||
|
options.remove(checkIntervalPushPos);
|
||||||
|
checkFrequencies = options.toArray(new SpinnerOption[options.size()]);
|
||||||
|
}
|
||||||
|
|
||||||
final ArrayAdapter<SpinnerOption> checkFrequenciesAdapter =
|
final ArrayAdapter<SpinnerOption> checkFrequenciesAdapter =
|
||||||
new ArrayAdapter<>(getActivity(), android.R.layout.simple_spinner_item,
|
new ArrayAdapter<>(getActivity(), android.R.layout.simple_spinner_item,
|
||||||
checkFrequencies);
|
checkFrequencies);
|
||||||
|
|
Loading…
Reference in New Issue