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
This commit is contained in:
parent
c084e8188d
commit
8c989772df
|
@ -60,6 +60,7 @@ public abstract class EmailServiceStatus {
|
||||||
public static final String SYNC_STATUS_TYPE = "type";
|
public static final String SYNC_STATUS_TYPE = "type";
|
||||||
public static final String SYNC_STATUS_ID = "id";
|
public static final String SYNC_STATUS_ID = "id";
|
||||||
public static final String SYNC_STATUS_CODE = "status_code";
|
public static final String SYNC_STATUS_CODE = "status_code";
|
||||||
|
public static final String SYNC_RESULT = "result";
|
||||||
public static final String SYNC_STATUS_PROGRESS = "progress";
|
public static final String SYNC_STATUS_PROGRESS = "progress";
|
||||||
|
|
||||||
// Values for the SYNC_STATUS_TYPE to specify what kind of sync status we're returning.
|
// 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,
|
private static void syncStatus(final ContentResolver cr, final Bundle syncExtras,
|
||||||
final int statusType, final long id, final int statusCode, final int progress,
|
final int statusType, final long id, final int statusCode, final int progress,
|
||||||
|
int syncResult,
|
||||||
final StatusWriter writer) {
|
final StatusWriter writer) {
|
||||||
final String callbackUri = syncExtras.getString(SYNC_EXTRAS_CALLBACK_URI);
|
final String callbackUri = syncExtras.getString(SYNC_EXTRAS_CALLBACK_URI);
|
||||||
final String callbackMethod = syncExtras.getString(SYNC_EXTRAS_CALLBACK_METHOD);
|
final String callbackMethod = syncExtras.getString(SYNC_EXTRAS_CALLBACK_METHOD);
|
||||||
|
@ -97,6 +99,9 @@ public abstract class EmailServiceStatus {
|
||||||
statusExtras.putInt(SYNC_STATUS_TYPE, statusType);
|
statusExtras.putInt(SYNC_STATUS_TYPE, statusType);
|
||||||
statusExtras.putLong(SYNC_STATUS_ID, id);
|
statusExtras.putLong(SYNC_STATUS_ID, id);
|
||||||
statusExtras.putInt(SYNC_STATUS_CODE, statusCode);
|
statusExtras.putInt(SYNC_STATUS_CODE, statusCode);
|
||||||
|
if (statusCode != IN_PROGRESS) {
|
||||||
|
statusExtras.putInt(SYNC_RESULT, syncResult);
|
||||||
|
}
|
||||||
statusExtras.putInt(SYNC_STATUS_PROGRESS, progress);
|
statusExtras.putInt(SYNC_STATUS_PROGRESS, progress);
|
||||||
if (writer != null) {
|
if (writer != null) {
|
||||||
writer.addToStatus(statusExtras);
|
writer.addToStatus(statusExtras);
|
||||||
|
@ -116,8 +121,9 @@ public abstract class EmailServiceStatus {
|
||||||
* @param progress The progress of this sync operation.
|
* @param progress The progress of this sync operation.
|
||||||
*/
|
*/
|
||||||
public static void syncMailboxStatus(final ContentResolver cr, final Bundle syncExtras,
|
public static void syncMailboxStatus(final ContentResolver cr, final Bundle syncExtras,
|
||||||
final long mailboxId, final int statusCode, final int progress) {
|
final long mailboxId, final int statusCode, final int progress, int syncResult) {
|
||||||
syncStatus(cr, syncExtras, SYNC_STATUS_TYPE_MAILBOX, mailboxId, statusCode, progress, null);
|
syncStatus(cr, syncExtras, SYNC_STATUS_TYPE_MAILBOX, mailboxId, statusCode, progress,
|
||||||
|
syncResult, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1911,8 +1911,22 @@ public class EmailProvider extends ContentProvider {
|
||||||
|
|
||||||
private void updateSyncStatus(final Bundle extras) {
|
private void updateSyncStatus(final Bundle extras) {
|
||||||
final long id = extras.getLong(EmailServiceStatus.SYNC_STATUS_ID);
|
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);
|
final Uri uri = ContentUris.withAppendedId(FOLDER_STATUS_URI, id);
|
||||||
EmailProvider.this.getContext().getContentResolver().notifyChange(uri, null);
|
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
|
@Override
|
||||||
|
@ -5178,6 +5192,26 @@ public class EmailProvider extends ContentProvider {
|
||||||
|
|
||||||
private Cursor uiFolderRefresh(final Mailbox mailbox, final int deltaMessageCount) {
|
private Cursor uiFolderRefresh(final Mailbox mailbox, final int deltaMessageCount) {
|
||||||
if (mailbox != null) {
|
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);
|
startSync(mailbox, deltaMessageCount);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -40,6 +40,7 @@ import com.android.emailcommon.provider.EmailContent.Message;
|
||||||
import com.android.emailcommon.provider.Mailbox;
|
import com.android.emailcommon.provider.Mailbox;
|
||||||
import com.android.emailcommon.service.EmailServiceProxy;
|
import com.android.emailcommon.service.EmailServiceProxy;
|
||||||
import com.android.emailcommon.service.EmailServiceStatus;
|
import com.android.emailcommon.service.EmailServiceStatus;
|
||||||
|
import com.android.mail.providers.UIProvider;
|
||||||
import com.android.mail.utils.LogUtils;
|
import com.android.mail.utils.LogUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -130,7 +131,7 @@ public class PopImapSyncAdapterService extends Service {
|
||||||
EmailServiceStub.sendMailImpl(context, account.mId);
|
EmailServiceStub.sendMailImpl(context, account.mId);
|
||||||
} else {
|
} else {
|
||||||
EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId,
|
EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId,
|
||||||
EmailServiceStatus.IN_PROGRESS, 0);
|
EmailServiceStatus.IN_PROGRESS, 0, UIProvider.LastSyncResult.SUCCESS);
|
||||||
final int status;
|
final int status;
|
||||||
if (protocol.equals(legacyImapProtocol)) {
|
if (protocol.equals(legacyImapProtocol)) {
|
||||||
status = ImapService.synchronizeMailboxSynchronous(context, account,
|
status = ImapService.synchronizeMailboxSynchronous(context, account,
|
||||||
|
@ -139,20 +140,28 @@ public class PopImapSyncAdapterService extends Service {
|
||||||
status = Pop3Service.synchronizeMailboxSynchronous(context, account,
|
status = Pop3Service.synchronizeMailboxSynchronous(context, account,
|
||||||
mailbox, deltaMessageCount);
|
mailbox, deltaMessageCount);
|
||||||
}
|
}
|
||||||
EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, status, 0);
|
EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, status, 0,
|
||||||
|
UIProvider.LastSyncResult.SUCCESS);
|
||||||
}
|
}
|
||||||
} catch (MessagingException e) {
|
} catch (MessagingException e) {
|
||||||
int cause = e.getExceptionType();
|
int cause = e.getExceptionType();
|
||||||
// XXX It's no good to put the MessagingException.cause here, that's not the
|
// XXX It's no good to put the MessagingException.cause here, that's not the
|
||||||
// same set of values that we use in EmailServiceStatus.
|
// same set of values that we use in EmailServiceStatus.
|
||||||
EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, cause, 0);
|
|
||||||
switch(cause) {
|
switch(cause) {
|
||||||
case MessagingException.IOERROR:
|
case MessagingException.IOERROR:
|
||||||
|
EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, cause, 0,
|
||||||
|
UIProvider.LastSyncResult.CONNECTION_ERROR);
|
||||||
syncResult.stats.numIoExceptions++;
|
syncResult.stats.numIoExceptions++;
|
||||||
break;
|
break;
|
||||||
case MessagingException.AUTHENTICATION_FAILED:
|
case MessagingException.AUTHENTICATION_FAILED:
|
||||||
|
EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, cause, 0,
|
||||||
|
UIProvider.LastSyncResult.AUTH_ERROR);
|
||||||
syncResult.stats.numAuthExceptions++;
|
syncResult.stats.numAuthExceptions++;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
EmailServiceStatus.syncMailboxStatus(resolver, extras, mailboxId, cause, 0,
|
||||||
|
UIProvider.LastSyncResult.INTERNAL_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
|
|
Loading…
Reference in New Issue