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.RemoteException;
|
||||
|
||||
import com.android.emailcommon.service.EmailServiceProxy;
|
||||
import com.android.emailcommon.utility.Utility;
|
||||
import com.android.mail.utils.LogUtils;
|
||||
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
|
||||
public static final int CHECK_INTERVAL_NEVER = -1;
|
||||
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 RESET_NEW_MESSAGE_COUNT_URI;
|
||||
public static Uri NOTIFIER_URI;
|
||||
public static Uri SYNC_SETTING_CHANGED_URI;
|
||||
|
||||
public static void initAccount() {
|
||||
CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/account");
|
||||
RESET_NEW_MESSAGE_COUNT_URI = Uri.parse(EmailContent.CONTENT_URI + "/resetNewMessageCount");
|
||||
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 mEmailAddress;
|
||||
public String mSyncKey;
|
||||
public int mSyncLookback;
|
||||
public int mSyncInterval;
|
||||
private int mSyncInterval;
|
||||
public long mHostAuthKeyRecv;
|
||||
public long mHostAuthKeySend;
|
||||
public int mFlags;
|
||||
@ -139,6 +144,7 @@ public final class Account extends EmailContent implements Parcelable {
|
||||
public String mSignature;
|
||||
public long mPolicyKey;
|
||||
public long mPingDuration;
|
||||
public int mCapabilities;
|
||||
|
||||
@VisibleForTesting
|
||||
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_PING_DURATION_COLUMN = 15;
|
||||
public static final int CONTENT_MAX_ATTACHMENT_SIZE_COLUMN = 16;
|
||||
public static final int CONTENT_CAPABILITIES_COLUMN = 17;
|
||||
|
||||
public static final String[] CONTENT_PROJECTION = {
|
||||
AttachmentColumns._ID, AccountColumns.DISPLAY_NAME,
|
||||
@ -181,7 +188,7 @@ public final class Account extends EmailContent implements Parcelable {
|
||||
AccountColumns.RINGTONE_URI, AccountColumns.PROTOCOL_VERSION,
|
||||
AccountColumns.SECURITY_SYNC_KEY,
|
||||
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;
|
||||
@ -279,6 +286,7 @@ public final class Account extends EmailContent implements Parcelable {
|
||||
mSignature = cursor.getString(CONTENT_SIGNATURE_COLUMN);
|
||||
mPolicyKey = cursor.getLong(CONTENT_POLICY_KEY_COLUMN);
|
||||
mPingDuration = cursor.getLong(CONTENT_PING_DURATION_COLUMN);
|
||||
mCapabilities = cursor.getInt(CONTENT_CAPABILITIES_COLUMN);
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
@ -367,7 +380,13 @@ public final class Account extends EmailContent implements Parcelable {
|
||||
* @param minutes the number of minutes between polling checks
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
@ -749,6 +782,7 @@ public final class Account extends EmailContent implements Parcelable {
|
||||
values.put(AccountColumns.SIGNATURE, mSignature);
|
||||
values.put(AccountColumns.POLICY_KEY, mPolicyKey);
|
||||
values.put(AccountColumns.PING_DURATION, mPingDuration);
|
||||
values.put(AccountColumns.CAPABILITIES, mCapabilities);
|
||||
return values;
|
||||
}
|
||||
|
||||
@ -779,6 +813,7 @@ public final class Account extends EmailContent implements Parcelable {
|
||||
json.putOpt(AccountColumns.PROTOCOL_VERSION, mProtocolVersion);
|
||||
json.putOpt(AccountColumns.SIGNATURE, mSignature);
|
||||
json.put(AccountColumns.PING_DURATION, mPingDuration);
|
||||
json.put(AccountColumns.CAPABILITIES, mCapabilities);
|
||||
return json;
|
||||
} catch (final JSONException e) {
|
||||
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);
|
||||
// POLICY_KEY is not stored
|
||||
a.mPingDuration = json.optInt(AccountColumns.PING_DURATION, 0);
|
||||
a.mCapabilities = json.optInt(AccountColumns.CAPABILITIES, 0);
|
||||
return a;
|
||||
} catch (final JSONException e) {
|
||||
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
|
||||
*/
|
||||
@ -903,6 +947,7 @@ public final class Account extends EmailContent implements Parcelable {
|
||||
} else {
|
||||
dest.writeByte((byte)0);
|
||||
}
|
||||
dest.writeInt(mCapabilities);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -937,6 +982,7 @@ public final class Account extends EmailContent implements Parcelable {
|
||||
if (in.readByte() == 1) {
|
||||
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
|
||||
// cursors (initially, the email AppWidget).
|
||||
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 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 Uri CONTENT_NOTIFIER_URI;
|
||||
public static Uri CONTENT_SYNC_SETTING_CHANGED_URI;
|
||||
public static Uri PICK_TRASH_FOLDER_URI;
|
||||
public static Uri PICK_SENT_FOLDER_URI;
|
||||
public static Uri MAILBOX_NOTIFICATION_URI;
|
||||
@ -175,8 +178,11 @@ public abstract class EmailContent {
|
||||
AUTHORITY = EMAIL_PACKAGE_NAME + ".provider";
|
||||
LogUtils.d("EmailContent", "init for " + AUTHORITY);
|
||||
NOTIFIER_AUTHORITY = EMAIL_PACKAGE_NAME + ".notifier";
|
||||
SYNC_SETTING_CHANGED_AUTHORITY = EMAIL_PACKAGE_NAME + ".sync_setting_changed";
|
||||
CONTENT_URI = Uri.parse("content://" + 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_SENT_FOLDER_URI = Uri.parse("content://" + AUTHORITY + "/pickSentFolder");
|
||||
MAILBOX_NOTIFICATION_URI = Uri.parse("content://" + AUTHORITY + "/mailboxNotification");
|
||||
@ -1724,6 +1730,8 @@ public abstract class EmailContent {
|
||||
public static final String PING_DURATION = "pingDuration";
|
||||
// Automatically fetch pop3 attachments
|
||||
public static final String AUTO_FETCH_ATTACHMENTS = "autoFetchAttachments";
|
||||
// Account capabilities (check EmailServiceProxy#CAPABILITY_*)
|
||||
public static final String CAPABILITIES = "capabilities";
|
||||
}
|
||||
|
||||
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 MESSAGE_COUNT_URI;
|
||||
public static Uri SYNC_SETTING_CHANGED_URI;
|
||||
|
||||
public static void initMailbox() {
|
||||
CONTENT_URI = Uri.parse(EmailContent.CONTENT_URI + "/mailbox");
|
||||
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) {
|
||||
|
@ -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_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 IEmailService mService;
|
||||
private final boolean isRemote;
|
||||
|
@ -165,6 +165,13 @@ public class EmailConnectivityManager extends BroadcastReceiver {
|
||||
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() {
|
||||
// If we're unregistered, throw an exception
|
||||
if (!mRegistered) {
|
||||
|
@ -107,7 +107,7 @@ public class AccountSettingsUtils {
|
||||
cv.put(AccountColumns.DISPLAY_NAME, account.getDisplayName());
|
||||
cv.put(AccountColumns.SENDER_NAME, account.getSenderName());
|
||||
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.SYNC_LOOKBACK, account.mSyncLookback);
|
||||
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 java.io.IOException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -50,6 +51,15 @@ class ImapConnection {
|
||||
// Always check in 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*/
|
||||
public static final int CAPABILITY_ID = 1 << 0;
|
||||
/** NAMESPACE capability per RFC 2342 */
|
||||
@ -58,6 +68,8 @@ class ImapConnection {
|
||||
public static final int CAPABILITY_STARTTLS = 1 << 2;
|
||||
/** UIDPLUS capability per RFC 4315 */
|
||||
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. */
|
||||
private int mCapabilities;
|
||||
@ -69,6 +81,8 @@ class ImapConnection {
|
||||
private String mAccessToken;
|
||||
private String mIdPhrase = null;
|
||||
|
||||
private boolean mIdling = false;
|
||||
|
||||
/** # of command/response lines to log upon crash. */
|
||||
private static final int DISCOURSE_LOGGER_SIZE = 64;
|
||||
private final DiscourseLogger mDiscourse = new DiscourseLogger(DISCOURSE_LOGGER_SIZE);
|
||||
@ -210,10 +224,23 @@ class ImapConnection {
|
||||
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.
|
||||
*/
|
||||
private boolean isCapable(int capability) {
|
||||
public boolean isCapable(int capability) {
|
||||
return (mCapabilities & capability) != 0;
|
||||
}
|
||||
|
||||
@ -235,6 +262,9 @@ class ImapConnection {
|
||||
if (capabilities.contains(ImapConstants.STARTTLS)) {
|
||||
mCapabilities |= CAPABILITY_STARTTLS;
|
||||
}
|
||||
if (capabilities.contains(ImapConstants.IDLE)) {
|
||||
mCapabilities |= CAPABILITY_IDLE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -273,6 +303,12 @@ class ImapConnection {
|
||||
*/
|
||||
String sendCommand(String command, boolean sensitive)
|
||||
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));
|
||||
open();
|
||||
return sendCommandInternal(command, sensitive);
|
||||
@ -284,7 +320,13 @@ class ImapConnection {
|
||||
throw new IOException("Null transport");
|
||||
}
|
||||
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);
|
||||
mDiscourse.addSentCommand(sensitive ? IMAP_REDACTED_LOG : commandToSend);
|
||||
return tag;
|
||||
@ -327,6 +369,11 @@ class ImapConnection {
|
||||
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
|
||||
*
|
||||
@ -336,13 +383,35 @@ class ImapConnection {
|
||||
*/
|
||||
List<ImapResponse> getCommandResponses() throws IOException, MessagingException {
|
||||
final List<ImapResponse> responses = new ArrayList<ImapResponse>();
|
||||
ImapResponse response;
|
||||
do {
|
||||
response = mParser.readResponse();
|
||||
responses.add(response);
|
||||
} while (!response.isTagged());
|
||||
ImapResponse response = null;
|
||||
boolean idling = false;
|
||||
boolean throwSocketTimeoutEx = true;
|
||||
int lastSocketTimeout = getReadTimeout();
|
||||
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 status = response.getStatusOrEmpty().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.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import static com.android.emailcommon.Logging.LOG_TAG;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
|
||||
import java.io.File;
|
||||
@ -60,6 +62,7 @@ import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -68,13 +71,39 @@ import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.TimeZone;
|
||||
|
||||
class ImapFolder extends Folder {
|
||||
public class ImapFolder extends Folder {
|
||||
private final static Flag[] PERMANENT_FLAGS =
|
||||
{ Flag.DELETED, Flag.SEEN, Flag.FLAGGED, Flag.ANSWERED };
|
||||
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 String mName;
|
||||
private int mMessageCount = -1;
|
||||
@ -86,6 +115,22 @@ class ImapFolder extends Folder {
|
||||
/** A set of hashes that can be used to track dirtiness */
|
||||
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) {
|
||||
mStore = store;
|
||||
mName = name;
|
||||
@ -176,6 +221,159 @@ class ImapFolder extends Folder {
|
||||
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
|
||||
public boolean exists() throws MessagingException {
|
||||
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
|
||||
public void delete(boolean recurse) {
|
||||
throw new Error("ImapStore.delete() not yet implemented");
|
||||
@ -1270,7 +1520,9 @@ class ImapFolder extends Folder {
|
||||
if (DebugUtils.DEBUG) {
|
||||
LogUtils.d(Logging.LOG_TAG, "IO Exception detected: ", ioe);
|
||||
}
|
||||
connection.close();
|
||||
if (connection != null) {
|
||||
connection.close();
|
||||
}
|
||||
if (connection == mConnection) {
|
||||
mConnection = null; // To prevent close() from returning the connection to the pool.
|
||||
close(false);
|
||||
@ -1278,6 +1530,127 @@ class ImapFolder extends Folder {
|
||||
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
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof ImapFolder) {
|
||||
|
@ -501,6 +501,14 @@ public class ImapStore extends Store {
|
||||
connection.destroyResponses();
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@ -556,6 +564,7 @@ public class ImapStore extends Store {
|
||||
while ((connection = mConnectionPool.poll()) != null) {
|
||||
try {
|
||||
connection.setStore(this);
|
||||
connection.setReadTimeout(MailTransport.SOCKET_READ_TIMEOUT);
|
||||
connection.executeSimpleCommand(ImapConstants.NOOP);
|
||||
break;
|
||||
} catch (MessagingException e) {
|
||||
|
@ -186,6 +186,10 @@ public class Pop3Store extends Store {
|
||||
ioe.getMessage());
|
||||
}
|
||||
bundle.putInt(EmailServiceProxy.VALIDATE_BUNDLE_RESULT_CODE, result);
|
||||
|
||||
// No special capabilities
|
||||
bundle.putInt(EmailServiceProxy.SETTINGS_BUNDLE_CAPABILITIES, 0);
|
||||
|
||||
return bundle;
|
||||
}
|
||||
|
||||
|
@ -46,6 +46,7 @@ public final class ImapConstants {
|
||||
public static final String COPYUID = "COPYUID";
|
||||
public static final String CREATE = "CREATE";
|
||||
public static final String DELETE = "DELETE";
|
||||
public static final String DONE = "DONE";
|
||||
public static final String EXAMINE = "EXAMINE";
|
||||
public static final String EXISTS = "EXISTS";
|
||||
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_SILENT = "FLAGS.SILENT";
|
||||
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 INTERNALDATE = "INTERNALDATE";
|
||||
public static final String LIST = "LIST";
|
||||
@ -73,6 +76,7 @@ public final class ImapConstants {
|
||||
public static final String PREAUTH = "PREAUTH";
|
||||
public static final String READ_ONLY = "READ-ONLY";
|
||||
public static final String READ_WRITE = "READ-WRITE";
|
||||
public static final String RECENT = "RECENT";
|
||||
public static final String RENAME = "RENAME";
|
||||
public static final String RFC822_SIZE = "RFC822.SIZE";
|
||||
public static final String SEARCH = "SEARCH";
|
||||
|
@ -180,7 +180,7 @@ public class ImapList extends ImapElement {
|
||||
|
||||
@Override
|
||||
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 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).
|
||||
* @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 boolean mIdling;
|
||||
private boolean mExpectIdlingResponse;
|
||||
|
||||
/**
|
||||
* Exception thrown when we receive BYE. It derives from IOException, so it'll be treated
|
||||
* in the same way EOF does.
|
||||
@ -168,10 +171,17 @@ public class ImapResponseParser {
|
||||
} catch (RuntimeException e) {
|
||||
// Parser crash -- log network activities.
|
||||
onParseError(e);
|
||||
mIdling = false;
|
||||
throw e;
|
||||
} catch (IOException e) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
@ -242,6 +252,14 @@ public class ImapResponseParser {
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void resetIdlingStatus() {
|
||||
mIdling = false;
|
||||
}
|
||||
|
||||
public void expectIdlingResponse() {
|
||||
mExpectIdlingResponse = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and return the response line.
|
||||
*/
|
||||
@ -263,11 +281,26 @@ public class ImapResponseParser {
|
||||
responseToDestroy = new ImapResponse(null, true);
|
||||
|
||||
// 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.
|
||||
responseToReturn = responseToDestroy;
|
||||
responseToDestroy = null;
|
||||
|
||||
mIdling = responseToReturn.isIdling();
|
||||
if (mIdling) {
|
||||
mExpectIdlingResponse = true;
|
||||
}
|
||||
} else {
|
||||
// Status response or response data
|
||||
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
|
||||
* 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.QuickResponse;
|
||||
import com.android.emailcommon.provider.SuggestedContact;
|
||||
import com.android.emailcommon.service.EmailServiceProxy;
|
||||
import com.android.emailcommon.service.LegacyPolicySet;
|
||||
import com.android.emailcommon.service.SyncWindow;
|
||||
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 above 12.0
|
||||
// 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.
|
||||
// Original version: 2
|
||||
@ -525,7 +527,8 @@ public final class DBHelper {
|
||||
+ AccountColumns.POLICY_KEY + " integer, "
|
||||
+ AccountColumns.MAX_ATTACHMENT_SIZE + " 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);
|
||||
// Deleting an account deletes associated Mailboxes and HostAuth's
|
||||
@ -1562,6 +1565,52 @@ public final class DBHelper {
|
||||
+ 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,
|
||||
// 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
|
||||
|
@ -189,11 +189,11 @@ public class EmailProvider extends ContentProvider
|
||||
"vnd.android.cursor.item/email-attachment";
|
||||
|
||||
/** 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 */
|
||||
private static final String NOTIFICATION_OP_INSERT = "insert";
|
||||
public static final String NOTIFICATION_OP_INSERT = "insert";
|
||||
/** 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. */
|
||||
protected static String QUERY_UIREFRESH = "uirefresh";
|
||||
@ -833,6 +833,7 @@ public class EmailProvider extends ContentProvider
|
||||
|
||||
// Notify all notifier cursors
|
||||
sendNotifierChange(getBaseNotificationUri(match), NOTIFICATION_OP_DELETE, id);
|
||||
sendSyncSettingChanged(getBaseSyncSettingChangedUri(match), NOTIFICATION_OP_DELETE, id);
|
||||
|
||||
// Notify all email content cursors
|
||||
notifyUI(EmailContent.CONTENT_URI, null);
|
||||
@ -1075,6 +1076,7 @@ public class EmailProvider extends ContentProvider
|
||||
|
||||
// Notify all notifier cursors
|
||||
sendNotifierChange(getBaseNotificationUri(match), NOTIFICATION_OP_INSERT, id);
|
||||
sendSyncSettingChanged(getBaseSyncSettingChangedUri(match), NOTIFICATION_OP_INSERT, id);
|
||||
|
||||
// Notify all existing cursors.
|
||||
notifyUI(EmailContent.CONTENT_URI, null);
|
||||
@ -1924,7 +1926,7 @@ public class EmailProvider extends ContentProvider
|
||||
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 db The {@link SQLiteDatabase}.
|
||||
* @param id The id of the thing we're looking for.
|
||||
@ -1937,9 +1939,13 @@ public class EmailProvider extends ContentProvider
|
||||
try {
|
||||
if (c.moveToFirst()) {
|
||||
final String protocol = c.getString(INDEX_PROTOCOL);
|
||||
// Only restart push for EAS accounts that have completed initial sync.
|
||||
if (context.getString(R.string.protocol_eas).equals(protocol) &&
|
||||
!EmailContent.isInitialSyncKey(c.getString(INDEX_SYNC_KEY))) {
|
||||
final String syncKey = c.getString(INDEX_SYNC_KEY);
|
||||
final boolean supportsPush =
|
||||
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 android.accounts.Account account =
|
||||
getAccountManagerAccount(context, emailAddress, protocol);
|
||||
@ -2010,6 +2016,7 @@ public class EmailProvider extends ContentProvider
|
||||
final SQLiteDatabase db = getDatabase(context);
|
||||
final int table = match >> BASE_SHIFT;
|
||||
int result;
|
||||
boolean syncSettingChanged = false;
|
||||
|
||||
// We do NOT allow setting of unreadCount/messageCount via the provider
|
||||
// These columns are maintained via triggers
|
||||
@ -2159,6 +2166,14 @@ public class EmailProvider extends ContentProvider
|
||||
}
|
||||
} else if (match == MESSAGE_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),
|
||||
selectionArgs);
|
||||
@ -2293,6 +2308,10 @@ public class EmailProvider extends ContentProvider
|
||||
TextUtils.isEmpty(values.getAsString(AttachmentColumns.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);
|
||||
break;
|
||||
@ -2314,6 +2333,10 @@ public class EmailProvider extends ContentProvider
|
||||
// Notify all notifier cursors if some records where changed in the database
|
||||
if (result > 0) {
|
||||
sendNotifierChange(getBaseNotificationUri(match), NOTIFICATION_OP_UPDATE, id);
|
||||
if (syncSettingChanged) {
|
||||
sendSyncSettingChanged(getBaseSyncSettingChangedUri(match),
|
||||
NOTIFICATION_OP_UPDATE, id);
|
||||
}
|
||||
notifyUI(notificationUri, null);
|
||||
}
|
||||
return result;
|
||||
@ -2544,6 +2567,21 @@ public class EmailProvider extends ContentProvider
|
||||
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
|
||||
* 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() {
|
||||
final Context context = getContext();
|
||||
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.Utils;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
@ -118,8 +119,9 @@ public class Utilities {
|
||||
ArrayList<Part> attachments = new ArrayList<Part>();
|
||||
MimeUtility.collectParts(message, viewables, attachments);
|
||||
|
||||
// Don't close the viewables attachment InputStream yet
|
||||
final ConversionUtilities.BodyFieldData data =
|
||||
ConversionUtilities.parseBodyFields(viewables);
|
||||
ConversionUtilities.parseBodyFields(viewables, false);
|
||||
|
||||
// set body and local message values
|
||||
localMessage.setFlags(data.isQuotedReply, data.isQuotedForward);
|
||||
@ -166,6 +168,21 @@ public class Utilities {
|
||||
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
|
||||
localMessage.mFlagLoaded = loadStatus;
|
||||
|
||||
|
@ -293,6 +293,10 @@ public class EmailBroadcastProcessorService extends IntentService {
|
||||
private void onBootCompleted() {
|
||||
performOneTimeInitialization();
|
||||
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() {
|
||||
|
@ -288,6 +288,12 @@ public abstract class EmailServiceStub extends IEmailService.Stub implements IEm
|
||||
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 the account/mailbox are gone, just report success; the UI handles this
|
||||
cb.loadAttachmentStatus(messageId, attachmentId,
|
||||
@ -416,7 +422,6 @@ public abstract class EmailServiceStub extends IEmailService.Stub implements IEm
|
||||
// actually occurs.
|
||||
mailbox.mUiSyncStatus = Mailbox.SYNC_STATUS_INITIAL_SYNC_NEEDED;
|
||||
}
|
||||
mailbox.save(mContext);
|
||||
if (type == Mailbox.TYPE_INBOX) {
|
||||
inboxId = mailbox.mId;
|
||||
|
||||
@ -425,6 +430,7 @@ public abstract class EmailServiceStub extends IEmailService.Stub implements IEm
|
||||
// should start marked
|
||||
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;
|
||||
|
||||
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 {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
private static final String TAG = "PopImapSyncService";
|
||||
private SyncAdapterImpl mSyncAdapter = null;
|
||||
private AbstractThreadedSyncAdapter mSyncAdapter = null;
|
||||
|
||||
private static String sPop3Protocol;
|
||||
private static String sLegacyImapProtocol;
|
||||
@ -58,7 +58,7 @@ public class PopImapSyncAdapterService extends Service {
|
||||
super();
|
||||
}
|
||||
|
||||
private static class SyncAdapterImpl extends AbstractThreadedSyncAdapter {
|
||||
static class SyncAdapterImpl extends AbstractThreadedSyncAdapter {
|
||||
public SyncAdapterImpl(Context context) {
|
||||
super(context, true /* autoInitialize */);
|
||||
}
|
||||
@ -71,10 +71,14 @@ public class PopImapSyncAdapterService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
public AbstractThreadedSyncAdapter getSyncAdapter() {
|
||||
return new SyncAdapterImpl(getApplicationContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
mSyncAdapter = new SyncAdapterImpl(getApplicationContext());
|
||||
mSyncAdapter = getSyncAdapter();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -101,14 +105,14 @@ public class PopImapSyncAdapterService extends Service {
|
||||
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 int deltaMessageCount) {
|
||||
TempDirectory.setTempDirectory(context);
|
||||
Mailbox mailbox = Mailbox.restoreMailboxWithId(context, mailboxId);
|
||||
if (mailbox == null) return;
|
||||
if (mailbox == null) return false;
|
||||
Account account = Account.restoreAccountWithId(context, mailbox.mAccountKey);
|
||||
if (account == null) return;
|
||||
if (account == null) return false;
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
if ((mailbox.mType != Mailbox.TYPE_OUTBOX) &&
|
||||
!loadsFromServer(context, mailbox, account)) {
|
||||
@ -116,7 +120,7 @@ public class PopImapSyncAdapterService extends Service {
|
||||
// updates table and return
|
||||
resolver.delete(Message.UPDATED_CONTENT_URI, MessageColumns.MAILBOX_KEY + "=?",
|
||||
new String[] {Long.toString(mailbox.mId)});
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
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,
|
||||
lastSyncResult);
|
||||
return true;
|
||||
}
|
||||
} catch (MessagingException e) {
|
||||
final int type = e.getExceptionType();
|
||||
@ -186,6 +191,7 @@ public class PopImapSyncAdapterService extends Service {
|
||||
values.put(Mailbox.SYNC_TIME, System.currentTimeMillis());
|
||||
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.
|
||||
if (info.offerLookback) {
|
||||
mailboxIds = getLoopBackMailboxIdsForSync(context, acct);
|
||||
} else {
|
||||
}
|
||||
if (mailboxIds.length == 0) {
|
||||
final long inboxId = Mailbox.findMailboxOfType(context, acct.mId,
|
||||
Mailbox.TYPE_INBOX);
|
||||
if (inboxId != Mailbox.NO_MAILBOX) {
|
||||
@ -262,9 +269,20 @@ public class PopImapSyncAdapterService extends Service {
|
||||
extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
|
||||
int deltaMessageCount =
|
||||
extras.getInt(Mailbox.SYNC_EXTRA_DELTA_MESSAGE_COUNT, 0);
|
||||
boolean success = mailboxIds.length > 0;
|
||||
for (long mailboxId : mailboxIds) {
|
||||
sync(context, mailboxId, extras, syncResult, uiRefresh,
|
||||
deltaMessageCount);
|
||||
boolean result = sync(context, mailboxId, extras, syncResult,
|
||||
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) {
|
||||
if (sLegacyImapProtocol == null) {
|
||||
sLegacyImapProtocol = ctx.getString(R.string.protocol_legacy_imap);
|
||||
|
@ -74,8 +74,8 @@
|
||||
email:serviceClass="com.android.email.service.ImapService"
|
||||
email:port="143"
|
||||
email:portSsl="993"
|
||||
email:syncIntervalStrings="@array/account_settings_check_frequency_entries"
|
||||
email:syncIntervals="@array/account_settings_check_frequency_values"
|
||||
email:syncIntervalStrings="@array/account_settings_check_frequency_entries_push"
|
||||
email:syncIntervals="@array/account_settings_check_frequency_values_push"
|
||||
email:defaultSyncInterval="mins15"
|
||||
|
||||
email:offerTls="true"
|
||||
|
@ -23,5 +23,6 @@
|
||||
<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:contentAuthority="@string/authority_email_provider"
|
||||
android:accountType="@string/account_manager_type_legacy_imap"
|
||||
android:allowParallelSyncs="true"
|
||||
android:supportsUploading="true"
|
||||
/>
|
||||
|
@ -417,6 +417,10 @@ public class AccountCheckSettingsFragment extends Fragment {
|
||||
EmailServiceProxy.VALIDATE_BUNDLE_ERROR_MESSAGE);
|
||||
return new MessagingException(resultCode, errorMessage);
|
||||
}
|
||||
|
||||
// Save account capabilities
|
||||
mAccount.mCapabilities = bundle.getInt(
|
||||
EmailServiceProxy.SETTINGS_BUNDLE_CAPABILITIES, 0);
|
||||
}
|
||||
|
||||
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.Mailbox;
|
||||
import com.android.emailcommon.provider.Policy;
|
||||
import com.android.emailcommon.service.EmailServiceProxy;
|
||||
import com.android.mail.preferences.AccountPreferences;
|
||||
import com.android.mail.preferences.FolderPreferences;
|
||||
import com.android.mail.preferences.FolderPreferences.NotificationLight;
|
||||
@ -84,7 +85,9 @@ import com.android.mail.utils.LogUtils;
|
||||
import com.android.mail.utils.NotificationUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
@ -243,10 +246,7 @@ public class AccountSettingsFragment extends MailAccountPrefsFragment
|
||||
final CharSequence [] syncIntervals =
|
||||
savedInstanceState.getCharSequenceArray(SAVESTATE_SYNC_INTERVALS);
|
||||
mCheckFrequency = (ListPreference) findPreference(PREFERENCE_FREQUENCY);
|
||||
if (mCheckFrequency != null) {
|
||||
mCheckFrequency.setEntries(syncIntervalStrings);
|
||||
mCheckFrequency.setEntryValues(syncIntervals);
|
||||
}
|
||||
fillCheckFrecuency(syncIntervalStrings, syncIntervals);
|
||||
}
|
||||
}
|
||||
|
||||
@ -382,16 +382,15 @@ public class AccountSettingsFragment extends MailAccountPrefsFragment
|
||||
final android.accounts.Account androidAcct = new android.accounts.Account(
|
||||
mAccount.mEmailAddress, mServiceInfo.accountType);
|
||||
if (Integer.parseInt(summary) == Account.CHECK_INTERVAL_NEVER) {
|
||||
// Disable syncing from the account manager. Leave the current sync frequency
|
||||
// in the database.
|
||||
// Disable syncing from the account manager.
|
||||
ContentResolver.setSyncAutomatically(androidAcct, EmailContent.AUTHORITY,
|
||||
false);
|
||||
} else {
|
||||
// Enable syncing from the account manager.
|
||||
ContentResolver.setSyncAutomatically(androidAcct, EmailContent.AUTHORITY,
|
||||
true);
|
||||
cv.put(AccountColumns.SYNC_INTERVAL, Integer.parseInt(summary));
|
||||
}
|
||||
cv.put(AccountColumns.SYNC_INTERVAL, Integer.parseInt(summary));
|
||||
}
|
||||
} else if (key.equals(PREFERENCE_SYNC_WINDOW)) {
|
||||
final String summary = newValue.toString();
|
||||
@ -749,8 +748,7 @@ public class AccountSettingsFragment extends MailAccountPrefsFragment
|
||||
R.string.preferences_signature_summary_not_set);
|
||||
|
||||
mCheckFrequency = (ListPreference) findPreference(PREFERENCE_FREQUENCY);
|
||||
mCheckFrequency.setEntries(mServiceInfo.syncIntervalStrings);
|
||||
mCheckFrequency.setEntryValues(mServiceInfo.syncIntervals);
|
||||
fillCheckFrecuency(mServiceInfo.syncIntervalStrings, mServiceInfo.syncIntervals);
|
||||
if (mServiceInfo.syncContacts || mServiceInfo.syncCalendar) {
|
||||
// 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.
|
||||
@ -1182,4 +1180,28 @@ public class AccountSettingsFragment extends MailAccountPrefsFragment
|
||||
}
|
||||
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) {
|
||||
final EmailServiceUtils.EmailServiceInfo info = mSetupData.getIncomingServiceInfo(this);
|
||||
if (info == null) return;
|
||||
account.mSyncInterval = info.defaultSyncInterval;
|
||||
account.setSyncInterval(info.defaultSyncInterval);
|
||||
account.mSyncLookback = info.defaultLookback;
|
||||
if (info.offerLocalDeletes) {
|
||||
account.setDeletePolicy(info.defaultLocalDeletes);
|
||||
|
@ -29,8 +29,13 @@ import com.android.email.activity.UiUtilities;
|
||||
import com.android.email.service.EmailServiceUtils;
|
||||
import com.android.emailcommon.provider.Account;
|
||||
import com.android.emailcommon.provider.Policy;
|
||||
import com.android.emailcommon.service.EmailServiceProxy;
|
||||
import com.android.emailcommon.service.SyncWindow;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class AccountSetupOptionsFragment extends AccountSetupFragment {
|
||||
private Spinner mCheckFrequencyView;
|
||||
private Spinner mSyncWindowView;
|
||||
@ -90,11 +95,24 @@ public class AccountSetupOptionsFragment extends AccountSetupFragment {
|
||||
final CharSequence[] frequencyEntries = serviceInfo.syncIntervalStrings;
|
||||
|
||||
// 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++) {
|
||||
checkFrequencies[i] = new SpinnerOption(
|
||||
Integer.valueOf(frequencyValues[i].toString()), frequencyEntries[i].toString());
|
||||
Integer value = Integer.valueOf(frequencyValues[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 =
|
||||
new ArrayAdapter<>(getActivity(), android.R.layout.simple_spinner_item,
|
||||
checkFrequencies);
|
||||
|
Loading…
Reference in New Issue
Block a user