summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlon Albert <aalbert@google.com>2013-10-17 17:46:26 -0700
committerAlon Albert <aalbert@google.com>2013-10-18 09:32:47 -0700
commit8c989772dfba08438650575f1ac2bb952bd56158 (patch)
treea813f16d582b689da558383cf4ac92185d7dfe60
parentc084e8188d87e57c109d246f81fccbc584900d54 (diff)
downloadandroid_packages_apps_Email-8c989772dfba08438650575f1ac2bb952bd56158.tar.gz
android_packages_apps_Email-8c989772dfba08438650575f1ac2bb952bd56158.tar.bz2
android_packages_apps_Email-8c989772dfba08438650575f1ac2bb952bd56158.zip
Handle User Refresh in Edge Cases
Handle the following edge cases when a manual refresh is triggered: * No connectivity * Low storage space * Timeout (sync not started) Bug: 11241113 Change-Id: I580235d633fcb65999c0bfe8bf383c9c8ba72110
-rw-r--r--emailcommon/src/com/android/emailcommon/service/EmailServiceStatus.java10
-rw-r--r--src/com/android/email/provider/EmailProvider.java34
-rw-r--r--src/com/android/email/provider/RefreshStatusMonitor.java159
-rw-r--r--src/com/android/email/service/PopImapSyncAdapterService.java15
4 files changed, 213 insertions, 5 deletions
diff --git a/emailcommon/src/com/android/emailcommon/service/EmailServiceStatus.java b/emailcommon/src/com/android/emailcommon/service/EmailServiceStatus.java
index b4f14b38a..553c6d46e 100644
--- a/emailcommon/src/com/android/emailcommon/service/EmailServiceStatus.java
+++ b/emailcommon/src/com/android/emailcommon/service/EmailServiceStatus.java
@@ -60,6 +60,7 @@ public abstract class EmailServiceStatus {
public static final String SYNC_STATUS_TYPE = "type";
public static final String SYNC_STATUS_ID = "id";
public static final String SYNC_STATUS_CODE = "status_code";
+ public static final String SYNC_RESULT = "result";
public static final String SYNC_STATUS_PROGRESS = "progress";
// Values for the SYNC_STATUS_TYPE to specify what kind of sync status we're returning.
@@ -88,6 +89,7 @@ public abstract class EmailServiceStatus {
*/
private static void syncStatus(final ContentResolver cr, final Bundle syncExtras,
final int statusType, final long id, final int statusCode, final int progress,
+ int syncResult,
final StatusWriter writer) {
final String callbackUri = syncExtras.getString(SYNC_EXTRAS_CALLBACK_URI);
final String callbackMethod = syncExtras.getString(SYNC_EXTRAS_CALLBACK_METHOD);
@@ -97,6 +99,9 @@ public abstract class EmailServiceStatus {
statusExtras.putInt(SYNC_STATUS_TYPE, statusType);
statusExtras.putLong(SYNC_STATUS_ID, id);
statusExtras.putInt(SYNC_STATUS_CODE, statusCode);
+ if (statusCode != IN_PROGRESS) {
+ statusExtras.putInt(SYNC_RESULT, syncResult);
+ }
statusExtras.putInt(SYNC_STATUS_PROGRESS, progress);
if (writer != null) {
writer.addToStatus(statusExtras);
@@ -116,8 +121,9 @@ public abstract class EmailServiceStatus {
* @param progress The progress of this sync operation.
*/
public static void syncMailboxStatus(final ContentResolver cr, final Bundle syncExtras,
- final long mailboxId, final int statusCode, final int progress) {
- syncStatus(cr, syncExtras, SYNC_STATUS_TYPE_MAILBOX, mailboxId, statusCode, progress, null);
+ final long mailboxId, final int statusCode, final int progress, int syncResult) {
+ syncStatus(cr, syncExtras, SYNC_STATUS_TYPE_MAILBOX, mailboxId, statusCode, progress,
+ syncResult, null);
}
}
diff --git a/src/com/android/email/provider/EmailProvider.java b/src/com/android/email/provider/EmailProvider.java
index a9bdbff5b..c91abab24 100644
--- a/src/com/android/email/provider/EmailProvider.java
+++ b/src/com/android/email/provider/EmailProvider.java
@@ -1911,8 +1911,22 @@ public class EmailProvider extends ContentProvider {
private void updateSyncStatus(final Bundle extras) {
final long id = extras.getLong(EmailServiceStatus.SYNC_STATUS_ID);
+ final int statusCode = extras.getInt(EmailServiceStatus.SYNC_STATUS_CODE);
final Uri uri = ContentUris.withAppendedId(FOLDER_STATUS_URI, id);
EmailProvider.this.getContext().getContentResolver().notifyChange(uri, null);
+ final boolean inProgress = statusCode == EmailServiceStatus.IN_PROGRESS;
+ if (inProgress) {
+ RefreshStatusMonitor.getInstance(getContext()).setSyncStarted(id);
+ } else {
+ final int result = extras.getInt(EmailServiceStatus.SYNC_RESULT);
+ final ContentValues values = new ContentValues();
+ values.put(Mailbox.UI_LAST_SYNC_RESULT, result);
+ mDatabase.update(
+ Mailbox.TABLE_NAME,
+ values,
+ WHERE_ID,
+ new String[] { String.valueOf(id) });
+ }
}
@Override
@@ -5178,6 +5192,26 @@ public class EmailProvider extends ContentProvider {
private Cursor uiFolderRefresh(final Mailbox mailbox, final int deltaMessageCount) {
if (mailbox != null) {
+ RefreshStatusMonitor.getInstance(getContext())
+ .monitorRefreshStatus(mailbox.mId, new RefreshStatusMonitor.Callback() {
+ @Override
+ public void onRefreshCompleted(long mailboxId, int result) {
+ final ContentValues values = new ContentValues();
+ values.put(Mailbox.UI_SYNC_STATUS, UIProvider.SyncStatus.NO_SYNC);
+ values.put(Mailbox.UI_LAST_SYNC_RESULT, result);
+ mDatabase.update(
+ Mailbox.TABLE_NAME,
+ values,
+ WHERE_ID,
+ new String[] { String.valueOf(mailboxId) });
+ notifyUIFolder(mailbox.mId, mailbox.mAccountKey);
+ }
+
+ @Override
+ public void onTimeout(long mailboxId) {
+ // todo
+ }
+ });
startSync(mailbox, deltaMessageCount);
}
return null;
diff --git a/src/com/android/email/provider/RefreshStatusMonitor.java b/src/com/android/email/provider/RefreshStatusMonitor.java
new file mode 100644
index 000000000..604d5c1dc
--- /dev/null
+++ b/src/com/android/email/provider/RefreshStatusMonitor.java
@@ -0,0 +1,159 @@
+package com.android.email.provider;
+
+import com.android.mail.providers.UIProvider;
+import com.android.mail.utils.LogTag;
+import com.android.mail.utils.LogUtils;
+import com.android.mail.utils.StorageLowState;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.os.Handler;
+import android.text.format.DateUtils;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class implements a singleton that monitors a mailbox refresh activated by the user.
+ * The refresh requests a sync but sometimes the sync doesn't happen till much later. This class
+ * checks if a sync has been started for a specific mailbox. It checks for no network connectivity
+ * and low storage conditions which prevent a sync and notifies the the caller using a callback.
+ * If no sync is started after a certain timeout, it gives up and notifies the caller.
+ */
+public class RefreshStatusMonitor {
+ private static final String TAG = LogTag.getLogTag();
+
+ private static final int REMOVE_REFRESH_STATUS_DELAY_MS = 250;
+ public static final long REMOVE_REFRESH_TIMEOUT_MS = DateUtils.MINUTE_IN_MILLIS;
+ private static final int MAX_RETRY =
+ (int) (REMOVE_REFRESH_TIMEOUT_MS / REMOVE_REFRESH_STATUS_DELAY_MS);
+
+ private static RefreshStatusMonitor sInstance = null;
+ private final Handler mHandler;
+ private boolean mIsStorageLow = false;
+ private final Map<Long, Boolean> mMailboxSync = new HashMap<Long, Boolean>();
+
+ private final Context mContext;
+
+ public static RefreshStatusMonitor getInstance(Context context) {
+ synchronized (RefreshStatusMonitor.class) {
+ if (sInstance == null) {
+ sInstance = new RefreshStatusMonitor(context.getApplicationContext());
+ }
+ }
+ return sInstance;
+ }
+
+ private RefreshStatusMonitor(Context context) {
+ mContext = context;
+ mHandler = new Handler(mContext.getMainLooper());
+ StorageLowState.registerHandler(new StorageLowState
+ .LowStorageHandler() {
+ @Override
+ public void onStorageLow() {
+ mIsStorageLow = true;
+ }
+
+ @Override
+ public void onStorageOk() {
+ mIsStorageLow = false;
+ }
+ });
+ }
+
+ public void monitorRefreshStatus(long mailboxId, Callback callback) {
+ synchronized (mMailboxSync) {
+ if (!mMailboxSync.containsKey(mailboxId))
+ mMailboxSync.put(mailboxId, false);
+ mHandler.postDelayed(
+ new RemoveRefreshStatusRunnable(mailboxId, callback),
+ REMOVE_REFRESH_STATUS_DELAY_MS);
+ }
+ }
+
+ public void setSyncStarted(long mailboxId) {
+ synchronized (mMailboxSync) {
+ // only if we're tracking this mailbox
+ if (mMailboxSync.containsKey(mailboxId)) {
+ LogUtils.d(TAG, "RefreshStatusMonitor: setSyncStarted: mailboxId=%d", mailboxId);
+ mMailboxSync.put(mailboxId, true);
+ }
+ }
+ }
+
+ private boolean isConnected() {
+ final ConnectivityManager connectivityManager =
+ ((ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE));
+ final NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
+ return (networkInfo != null) && networkInfo.isConnected();
+ }
+
+ private class RemoveRefreshStatusRunnable implements Runnable {
+ private final long mMailboxId;
+ private final Callback mCallback;
+
+ private int mNumRetries = 0;
+
+
+ RemoveRefreshStatusRunnable(long mailboxId, Callback callback) {
+ mMailboxId = mailboxId;
+ mCallback = callback;
+ }
+
+ @Override
+ public void run() {
+ synchronized (mMailboxSync) {
+ final Boolean isSyncRunning = mMailboxSync.get(mMailboxId);
+ if (Boolean.FALSE.equals(isSyncRunning)) {
+ if (mIsStorageLow) {
+ LogUtils.d(TAG, "RefreshStatusMonitor: mailboxId=%d LOW STORAGE",
+ mMailboxId);
+ // The device storage is low and sync will never succeed.
+ mCallback.onRefreshCompleted(
+ mMailboxId, UIProvider.LastSyncResult.STORAGE_ERROR);
+ mMailboxSync.remove(mMailboxId);
+ } else if (!isConnected()) {
+ LogUtils.d(TAG, "RefreshStatusMonitor: mailboxId=%d NOT CONNECTED",
+ mMailboxId);
+ // The device is not connected to the Internet. A sync will never succeed.
+ mCallback.onRefreshCompleted(
+ mMailboxId, UIProvider.LastSyncResult.CONNECTION_ERROR);
+ mMailboxSync.remove(mMailboxId);
+ } else {
+ // The device is connected to the Internet. It might take a short while for
+ // the sync manager to initiate our sync, so let's post this runnable again
+ // and hope that we have started syncing by then.
+ mNumRetries++;
+ LogUtils.d(TAG, "RefreshStatusMonitor: mailboxId=%d Retry %d",
+ mMailboxId, mNumRetries);
+ if (mNumRetries > MAX_RETRY) {
+ LogUtils.d(TAG, "RefreshStatusMonitor: mailboxId=%d TIMEOUT",
+ mMailboxId);
+ // Hide the sync status bar if it's been a while since sync was
+ // requested and still hasn't started.
+ mMailboxSync.remove(mMailboxId);
+ mCallback.onTimeout(mMailboxId);
+ // TODO: Displaying a user friendly message in addition.
+ } else {
+ mHandler.postDelayed(this, REMOVE_REFRESH_STATUS_DELAY_MS);
+ }
+ }
+ } else {
+ // Some sync is currently in progress. We're done
+ LogUtils.d(TAG, "RefreshStatusMonitor: mailboxId=%d SYNC DETECTED", mMailboxId);
+ // it's not quite a success yet, the sync just started but we need to clear the
+ // error so the retry bar goes away.
+ mCallback.onRefreshCompleted(
+ mMailboxId, UIProvider.LastSyncResult.SUCCESS);
+ mMailboxSync.remove(mMailboxId);
+ }
+ }
+ }
+ }
+
+ public interface Callback {
+ void onRefreshCompleted(long mailboxId, int result);
+ void onTimeout(long mailboxId);
+ }
+}
diff --git a/src/com/android/email/service/PopImapSyncAdapterService.java b/src/com/android/email/service/PopImapSyncAdapterService.java
index 8a95af736..4099be5c2 100644
--- a/src/com/android/email/service/PopImapSyncAdapterService.java
+++ b/src/com/android/email/service/PopImapSyncAdapterService.java
@@ -40,6 +40,7 @@ import com.android.emailcommon.provider.EmailContent.Message;
import com.android.emailcommon.provider.Mailbox;
import com.android.emailcommon.service.EmailServiceProxy;
import com.android.emailcommon.service.EmailServiceStatus;
+import com.android.mail.providers.UIProvider;
import com.android.mail.utils.LogUtils;
import java.util.ArrayList;
@@ -130,7 +131,7 @@ public class PopImapSyncAdapterService extends Service {
EmailServiceStub.sendMailImpl(context, account.mId);
} else {
EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId,
- EmailServiceStatus.IN_PROGRESS, 0);
+ EmailServiceStatus.IN_PROGRESS, 0, UIProvider.LastSyncResult.SUCCESS);
final int status;
if (protocol.equals(legacyImapProtocol)) {
status = ImapService.synchronizeMailboxSynchronous(context, account,
@@ -139,20 +140,28 @@ public class PopImapSyncAdapterService extends Service {
status = Pop3Service.synchronizeMailboxSynchronous(context, account,
mailbox, deltaMessageCount);
}
- EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, status, 0);
+ EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, status, 0,
+ UIProvider.LastSyncResult.SUCCESS);
}
} catch (MessagingException e) {
int cause = e.getExceptionType();
// XXX It's no good to put the MessagingException.cause here, that's not the
// same set of values that we use in EmailServiceStatus.
- EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, cause, 0);
switch(cause) {
case MessagingException.IOERROR:
+ EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, cause, 0,
+ UIProvider.LastSyncResult.CONNECTION_ERROR);
syncResult.stats.numIoExceptions++;
break;
case MessagingException.AUTHENTICATION_FAILED:
+ EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, cause, 0,
+ UIProvider.LastSyncResult.AUTH_ERROR);
syncResult.stats.numAuthExceptions++;
break;
+
+ default:
+ EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, cause, 0,
+ UIProvider.LastSyncResult.INTERNAL_ERROR);
}
}
} finally {