Merge "Postpone error until no more retries are available" into nyc-mr1-dev
diff --git a/src/com/android/phone/VoicemailStatus.java b/src/com/android/phone/VoicemailStatus.java
index 062e734..2d8bcc3 100644
--- a/src/com/android/phone/VoicemailStatus.java
+++ b/src/com/android/phone/VoicemailStatus.java
@@ -23,7 +23,6 @@
 import android.provider.VoicemailContract;
 import android.provider.VoicemailContract.Status;
 import android.telecom.PhoneAccountHandle;
-
 import com.android.phone.vvm.omtp.utils.PhoneAccountHandleConverter;
 
 public class VoicemailStatus {
@@ -41,6 +40,10 @@
             mPhoneAccountHandle = phoneAccountHandle;
         }
 
+        public PhoneAccountHandle getPhoneAccountHandle() {
+            return mPhoneAccountHandle;
+        }
+
         public Editor setType(String type) {
             mValues.put(Status.SOURCE_TYPE, type);
             return this;
@@ -79,6 +82,33 @@
             ContentResolver contentResolver = mContext.getContentResolver();
             Uri statusUri = VoicemailContract.Status.buildSourceUri(mContext.getPackageName());
             contentResolver.insert(statusUri, mValues);
+            mValues.clear();
+        }
+
+        public ContentValues getValues() {
+            return mValues;
+        }
+    }
+
+    /**
+     * A voicemail status editor that the decision of whether to actually write to the database can
+     * be deferred. This object will be passed around as a usual {@link Editor}, but {@link
+     * #apply()} doesn't do anything. If later the creator of this object decides any status changes
+     * written to it should be committed, {@link #deferredApply()} should be called.
+     */
+    public static class DeferredEditor extends Editor {
+
+        private DeferredEditor(Context context, PhoneAccountHandle phoneAccountHandle) {
+            super(context, phoneAccountHandle);
+        }
+
+        @Override
+        public void apply() {
+            // Do nothing
+        }
+
+        public void deferredApply() {
+            super.apply();
         }
     }
 
@@ -89,4 +119,8 @@
     public static Editor edit(Context context, int subId) {
         return new Editor(context, PhoneAccountHandleConverter.fromSubId(subId));
     }
+
+    public static DeferredEditor deferredEdit(Context context, int subId) {
+        return new DeferredEditor(context, PhoneAccountHandleConverter.fromSubId(subId));
+    }
 }
diff --git a/src/com/android/phone/settings/VoicemailChangePinActivity.java b/src/com/android/phone/settings/VoicemailChangePinActivity.java
index a963af2..74adb12 100644
--- a/src/com/android/phone/settings/VoicemailChangePinActivity.java
+++ b/src/com/android/phone/settings/VoicemailChangePinActivity.java
@@ -46,6 +46,7 @@
 import android.widget.Toast;
 import com.android.phone.PhoneUtils;
 import com.android.phone.R;
+import com.android.phone.VoicemailStatus;
 import com.android.phone.common.mail.MessagingException;
 import com.android.phone.vvm.omtp.OmtpConstants;
 import com.android.phone.vvm.omtp.OmtpConstants.ChangePinResult;
@@ -181,7 +182,7 @@
                     // Wipe the default old PIN so the old PIN input box will be shown to the user
                     // on the next time.
                     setDefaultOldPIN(activity, activity.mPhoneAccountHandle, null);
-                    activity.mConfig.handleEvent(OmtpEvents.CONFIG_PIN_SET);
+                    activity.handleOmtpEvent(OmtpEvents.CONFIG_PIN_SET);
                     activity.updateState(State.EnterOldPin);
                 }
             }
@@ -271,7 +272,7 @@
                     // Wipe the default old PIN so the old PIN input box will be shown to the user
                     // on the next time.
                     setDefaultOldPIN(activity, activity.mPhoneAccountHandle, null);
-                    activity.mConfig.handleEvent(OmtpEvents.CONFIG_PIN_SET);
+                    activity.handleOmtpEvent(OmtpEvents.CONFIG_PIN_SET);
 
                     activity.finish();
 
@@ -377,6 +378,16 @@
         }
     }
 
+    private void handleOmtpEvent(OmtpEvents event) {
+        mConfig.handleEvent(getVoicemailStatusEditor(), event);
+    }
+
+    private VoicemailStatus.Editor getVoicemailStatusEditor() {
+        // This activity does not have any automatic retry mechanism, errors should be written right
+        // away.
+        return VoicemailStatus.edit(this, mPhoneAccountHandle);
+    }
+
     /**
      * Extracts the pin length requirement sent by the server with a STATUS SMS.
      */
@@ -588,7 +599,8 @@
         private final String mNewPin;
 
         public ChangePinNetworkRequestCallback(String oldPin, String newPin) {
-            super(mConfig, mPhoneAccountHandle);
+            super(mConfig, mPhoneAccountHandle,
+                VoicemailChangePinActivity.this.getVoicemailStatusEditor());
             mOldPin = oldPin;
             mNewPin = newPin;
         }
@@ -597,7 +609,8 @@
         public void onAvailable(Network network) {
             super.onAvailable(network);
             try (ImapHelper helper =
-                new ImapHelper(VoicemailChangePinActivity.this, mPhoneAccountHandle, network)){
+                new ImapHelper(VoicemailChangePinActivity.this, mPhoneAccountHandle, network,
+                    getVoicemailStatusEditor())) {
 
                 @ChangePinResult int result =
                         helper.changePin(mOldPin, mNewPin);
diff --git a/src/com/android/phone/vvm/omtp/ActivationTask.java b/src/com/android/phone/vvm/omtp/ActivationTask.java
index b575b95..5998950 100644
--- a/src/com/android/phone/vvm/omtp/ActivationTask.java
+++ b/src/com/android/phone/vvm/omtp/ActivationTask.java
@@ -29,6 +29,7 @@
 import android.telephony.TelephonyManager;
 import com.android.phone.Assert;
 import com.android.phone.PhoneGlobals;
+import com.android.phone.VoicemailStatus;
 import com.android.phone.vvm.omtp.protocol.VisualVoicemailProtocol;
 import com.android.phone.vvm.omtp.scheduling.BaseTask;
 import com.android.phone.vvm.omtp.scheduling.RetryPolicy;
@@ -63,11 +64,14 @@
     @Nullable
     private static DeviceProvisionedObserver sDeviceProvisionedObserver;
 
+    private final RetryPolicy mRetryPolicy;
+
     private Bundle mData;
 
     public ActivationTask() {
         super(TASK_ACTIVATION);
-        addPolicy(new RetryPolicy(RETRY_TIMES, RETRY_INTERVAL_MILLIS));
+        mRetryPolicy = new RetryPolicy(RETRY_TIMES, RETRY_INTERVAL_MILLIS);
+        addPolicy(mRetryPolicy);
     }
 
     /**
@@ -123,9 +127,18 @@
         int subId = getSubId();
 
         OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(getContext(), subId);
-        helper.handleEvent(OmtpEvents.CONFIG_ACTIVATING);
-        helper.activateSmsFilter();
         PhoneAccountHandle phoneAccountHandle = PhoneAccountHandleConverter.fromSubId(subId);
+        if (!OmtpVvmSourceManager.getInstance(getContext())
+                .isVvmSourceRegistered(phoneAccountHandle)) {
+            // Only show the "activating" message if activation has not been completed before in
+            // this boot. Subsequent activations are more of a status check and usually does not
+            // concern the user.
+            helper.handleEvent(VoicemailStatus.edit(getContext(), subId),
+                    OmtpEvents.CONFIG_ACTIVATING);
+        }
+
+        helper.activateSmsFilter();
+        VoicemailStatus.Editor status = mRetryPolicy.getVoicemailStatusEditor();
 
         VisualVoicemailProtocol protocol = helper.getProtocol();
 
@@ -144,7 +157,7 @@
             } catch (TimeoutException e) {
                 // The carrier is expected to return an STATUS SMS within STATUS_SMS_TIMEOUT_MILLIS
                 // handleEvent() will do the logging.
-                helper.handleEvent(OmtpEvents.CONFIG_STATUS_SMS_TIME_OUT);
+                helper.handleEvent(status, OmtpEvents.CONFIG_STATUS_SMS_TIME_OUT);
                 fail();
                 return;
             } catch (InterruptedException | ExecutionException | IOException e) {
@@ -160,32 +173,32 @@
 
         if (message.getProvisioningStatus().equals(OmtpConstants.SUBSCRIBER_READY)) {
             VvmLog.d(TAG, "subscriber ready, no activation required");
-            updateSource(getContext(), phoneAccountHandle, getSubId(), message);
+            updateSource(getContext(), phoneAccountHandle, getSubId(), status, message);
         } else {
             if (helper.supportsProvisioning()) {
                 VvmLog.i(TAG, "Subscriber not ready, start provisioning");
-                helper.startProvisioning(this, phoneAccountHandle, message, data);
+                helper.startProvisioning(this, phoneAccountHandle, status, message, data);
 
             } else if (message.getProvisioningStatus().equals(OmtpConstants.SUBSCRIBER_NEW)) {
                 VvmLog.i(TAG, "Subscriber new but provisioning is not supported");
                 // Ignore the non-ready state and attempt to use the provided info as is.
                 // This is probably caused by not completing the new user tutorial.
-                updateSource(getContext(), phoneAccountHandle, getSubId(), message);
+                updateSource(getContext(), phoneAccountHandle, getSubId(), status, message);
             } else {
                 VvmLog.i(TAG, "Subscriber not ready but provisioning is not supported");
-                helper.handleEvent(OmtpEvents.CONFIG_SERVICE_NOT_AVAILABLE);
+                helper.handleEvent(status, OmtpEvents.CONFIG_SERVICE_NOT_AVAILABLE);
             }
         }
     }
 
     public static void updateSource(Context context, PhoneAccountHandle phone, int subId,
-            StatusMessage message) {
+            VoicemailStatus.Editor status, StatusMessage message) {
         OmtpVvmSourceManager vvmSourceManager =
                 OmtpVvmSourceManager.getInstance(context);
 
         if (OmtpConstants.SUCCESS.equals(message.getReturnCode())) {
             OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(context, subId);
-            helper.handleEvent(OmtpEvents.CONFIG_REQUEST_STATUS_SUCCESS);
+            helper.handleEvent(status, OmtpEvents.CONFIG_REQUEST_STATUS_SUCCESS);
 
             // Save the IMAP credentials in preferences so they are persistent and can be retrieved.
             VisualVoicemailPreferences prefs = new VisualVoicemailPreferences(context, phone);
diff --git a/src/com/android/phone/vvm/omtp/DefaultOmtpEventHandler.java b/src/com/android/phone/vvm/omtp/DefaultOmtpEventHandler.java
index 353ba07..88943c9 100644
--- a/src/com/android/phone/vvm/omtp/DefaultOmtpEventHandler.java
+++ b/src/com/android/phone/vvm/omtp/DefaultOmtpEventHandler.java
@@ -27,31 +27,31 @@
     private static final String TAG = "DefErrorCodeHandler";
 
     public static void handleEvent(Context context, OmtpVvmCarrierConfigHelper config,
-            OmtpEvents event) {
+        VoicemailStatus.Editor status, OmtpEvents event) {
         switch (event.getType()) {
             case Type.CONFIGURATION:
-                handleConfigurationEvent(context, config, event);
+                handleConfigurationEvent(context, status, event);
                 break;
             case Type.DATA_CHANNEL:
-                handleDataChannelEvent(context, config, event);
+                handleDataChannelEvent(context, status, event);
                 break;
             case Type.NOTIFICATION_CHANNEL:
-                handleNotificationChannelEvent(context, config, event);
+                handleNotificationChannelEvent(context, config, status, event);
                 break;
             case Type.OTHER:
-                handleOtherEvent(context, config, event);
+                handleOtherEvent(context, status, event);
                 break;
             default:
                 VvmLog.wtf(TAG, "invalid event type " + event.getType() + " for " + event);
         }
     }
 
-    private static void handleConfigurationEvent(Context context, OmtpVvmCarrierConfigHelper config,
+    private static void handleConfigurationEvent(Context context, VoicemailStatus.Editor status,
             OmtpEvents event) {
         switch (event) {
             case CONFIG_REQUEST_STATUS_SUCCESS:
             case CONFIG_PIN_SET:
-                VoicemailStatus.edit(context, config.getSubId())
+                status
                         .setConfigurationState(VoicemailContract.Status.CONFIGURATION_STATE_OK)
                         .setNotificationChannelState(Status.NOTIFICATION_CHANNEL_STATE_OK)
                         .apply();
@@ -59,18 +59,18 @@
             case CONFIG_ACTIVATING:
                 // Wipe all errors from the last activation. All errors shown should be new errors
                 // for this activation.
-                VoicemailStatus.edit(context, config.getSubId())
+                status
                         .setConfigurationState(Status.CONFIGURATION_STATE_CONFIGURING)
                         .setDataChannelState(Status.DATA_CHANNEL_STATE_OK)
                         .setNotificationChannelState(Status.NOTIFICATION_CHANNEL_STATE_OK).apply();
                 break;
             case CONFIG_SERVICE_NOT_AVAILABLE:
-                VoicemailStatus.edit(context, config.getSubId())
+                status
                     .setConfigurationState(Status.CONFIGURATION_STATE_FAILED)
                     .apply();
                 break;
             case CONFIG_STATUS_SMS_TIME_OUT:
-                VoicemailStatus.edit(context, config.getSubId())
+                status
                         .setConfigurationState(Status.CONFIGURATION_STATE_FAILED)
                         .apply();
                 break;
@@ -79,36 +79,36 @@
         }
     }
 
-    private static void handleDataChannelEvent(Context context, OmtpVvmCarrierConfigHelper config,
+    private static void handleDataChannelEvent(Context context, VoicemailStatus.Editor status,
             OmtpEvents event) {
         switch (event) {
             case DATA_IMAP_OPERATION_STARTED:
             case DATA_IMAP_OPERATION_COMPLETED:
-                VoicemailStatus.edit(context, config.getSubId())
+                status
                         .setDataChannelState(Status.DATA_CHANNEL_STATE_OK)
                         .apply();
                 break;
 
             case DATA_NO_CONNECTION:
-                VoicemailStatus.edit(context, config.getSubId())
+                status
                         .setDataChannelState(Status.DATA_CHANNEL_STATE_NO_CONNECTION)
                         .apply();
                 break;
 
             case DATA_NO_CONNECTION_CELLULAR_REQUIRED:
-                VoicemailStatus.edit(context, config.getSubId())
+                status
                         .setDataChannelState(
                                 Status.DATA_CHANNEL_STATE_NO_CONNECTION_CELLULAR_REQUIRED)
                         .apply();
                 break;
             case DATA_INVALID_PORT:
-                VoicemailStatus.edit(context, config.getSubId())
+                status
                         .setDataChannelState(
                                 VoicemailContract.Status.DATA_CHANNEL_STATE_BAD_CONFIGURATION)
                         .apply();
                 break;
             case DATA_CANNOT_RESOLVE_HOST_ON_NETWORK:
-                VoicemailStatus.edit(context, config.getSubId())
+                status
                         .setDataChannelState(
                                 VoicemailContract.Status.DATA_CHANNEL_STATE_SERVER_CONNECTION_ERROR)
                         .apply();
@@ -116,7 +116,7 @@
             case DATA_SSL_INVALID_HOST_NAME:
             case DATA_CANNOT_ESTABLISH_SSL_SESSION:
             case DATA_IOE_ON_OPEN:
-                VoicemailStatus.edit(context, config.getSubId())
+                status
                         .setDataChannelState(
                                 VoicemailContract.Status.DATA_CHANNEL_STATE_COMMUNICATION_ERROR)
                         .apply();
@@ -129,7 +129,7 @@
             case DATA_AUTH_SERVICE_NOT_PROVISIONED:
             case DATA_AUTH_SERVICE_NOT_ACTIVATED:
             case DATA_AUTH_USER_IS_BLOCKED:
-                VoicemailStatus.edit(context, config.getSubId())
+                status
                         .setDataChannelState(
                                 VoicemailContract.Status.DATA_CHANNEL_STATE_BAD_CONFIGURATION)
                         .apply();
@@ -139,7 +139,7 @@
             case DATA_INVALID_INITIAL_SERVER_RESPONSE:
             case DATA_SSL_EXCEPTION:
             case DATA_ALL_SOCKET_CONNECTION_FAILED:
-                VoicemailStatus.edit(context, config.getSubId())
+                status
                         .setDataChannelState(
                                 VoicemailContract.Status.DATA_CHANNEL_STATE_SERVER_ERROR)
                         .apply();
@@ -151,10 +151,10 @@
     }
 
     private static void handleNotificationChannelEvent(Context context,
-            OmtpVvmCarrierConfigHelper config, OmtpEvents event) {
+        OmtpVvmCarrierConfigHelper config, VoicemailStatus.Editor status, OmtpEvents event) {
         switch (event) {
             case NOTIFICATION_IN_SERVICE:
-                VoicemailStatus.edit(context, config.getSubId())
+                status
                         .setNotificationChannelState(Status.NOTIFICATION_CHANNEL_STATE_OK)
                         // Clear the error state. A sync should follow signal return so any error
                         // will be reposted.
@@ -162,24 +162,23 @@
                         .apply();
                 break;
             case NOTIFICATION_SERVICE_LOST:
-                VoicemailStatus.Editor editor = VoicemailStatus.edit(context, config.getSubId());
-                editor.setNotificationChannelState(Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION);
+                status.setNotificationChannelState(Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION);
                 if (config.isCellularDataRequired()) {
-                    editor.setDataChannelState(
+                    status.setDataChannelState(
                             Status.DATA_CHANNEL_STATE_NO_CONNECTION_CELLULAR_REQUIRED);
                 }
-                editor.apply();
+                status.apply();
                 break;
             default:
                 VvmLog.wtf(TAG, "invalid notification channel event " + event);
         }
     }
 
-    private static void handleOtherEvent(Context context, OmtpVvmCarrierConfigHelper config,
+    private static void handleOtherEvent(Context context, VoicemailStatus.Editor status,
             OmtpEvents event) {
         switch (event) {
             case OTHER_SOURCE_REMOVED:
-                VoicemailStatus.edit(context, config.getSubId())
+                status
                         .setConfigurationState(Status.CONFIGURATION_STATE_NOT_CONFIGURED)
                         .setNotificationChannelState(
                                 Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION)
diff --git a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
index 00a70be..5d06d1e 100644
--- a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
+++ b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
@@ -342,9 +342,9 @@
     }
 
     public void startProvisioning(ActivationTask task, PhoneAccountHandle phone,
-            StatusMessage message, Bundle data) {
+        VoicemailStatus.Editor status, StatusMessage message, Bundle data) {
         if (mProtocol != null) {
-            mProtocol.startProvisioning(task, phone, this, message, data);
+            mProtocol.startProvisioning(task, phone, this, status, message, data);
         }
     }
 
@@ -354,10 +354,10 @@
         }
     }
 
-    public void handleEvent(OmtpEvents event) {
+    public void handleEvent(VoicemailStatus.Editor status, OmtpEvents event) {
         VvmLog.i(TAG, "OmtpEvent:" + event);
         if (mProtocol != null) {
-            mProtocol.handleEvent(mContext, this, event);
+            mProtocol.handleEvent(mContext, this, status, event);
         }
     }
 
diff --git a/src/com/android/phone/vvm/omtp/VvmPhoneStateListener.java b/src/com/android/phone/vvm/omtp/VvmPhoneStateListener.java
index 1cb23d4..f64d15b 100644
--- a/src/com/android/phone/vvm/omtp/VvmPhoneStateListener.java
+++ b/src/com/android/phone/vvm/omtp/VvmPhoneStateListener.java
@@ -19,9 +19,9 @@
 import android.telecom.PhoneAccountHandle;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
-
 import com.android.phone.PhoneGlobals;
 import com.android.phone.PhoneUtils;
+import com.android.phone.VoicemailStatus;
 import com.android.phone.vvm.omtp.sync.OmtpVvmSourceManager;
 import com.android.phone.vvm.omtp.sync.OmtpVvmSyncService;
 import com.android.phone.vvm.omtp.sync.SyncTask;
@@ -66,7 +66,8 @@
                 if (!voicemailStatusQueryHelper.isNotificationsChannelActive(mPhoneAccount)) {
                     VvmLog
                             .v(TAG, "Notifications channel is active for " + subId);
-                    helper.handleEvent(OmtpEvents.NOTIFICATION_IN_SERVICE);
+                    helper.handleEvent(VoicemailStatus.edit(mContext, mPhoneAccount),
+                        OmtpEvents.NOTIFICATION_IN_SERVICE);
                     PhoneGlobals.getInstance().clearMwiIndicator(subId);
                 }
             }
@@ -91,7 +92,8 @@
             if (!OmtpVvmSourceManager.getInstance(mContext).isVvmSourceRegistered(mPhoneAccount)) {
                 return;
             }
-            helper.handleEvent(OmtpEvents.NOTIFICATION_SERVICE_LOST);
+            helper.handleEvent(VoicemailStatus.edit(mContext, mPhoneAccount),
+                OmtpEvents.NOTIFICATION_SERVICE_LOST);
         }
         mPreviousState = state;
     }
diff --git a/src/com/android/phone/vvm/omtp/fetch/FetchVoicemailReceiver.java b/src/com/android/phone/vvm/omtp/fetch/FetchVoicemailReceiver.java
index 5ec190f..d5e627f 100644
--- a/src/com/android/phone/vvm/omtp/fetch/FetchVoicemailReceiver.java
+++ b/src/com/android/phone/vvm/omtp/fetch/FetchVoicemailReceiver.java
@@ -30,15 +30,14 @@
 import android.telecom.PhoneAccountHandle;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
-
 import com.android.phone.PhoneUtils;
+import com.android.phone.VoicemailStatus;
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
 import com.android.phone.vvm.omtp.VvmLog;
 import com.android.phone.vvm.omtp.imap.ImapHelper;
 import com.android.phone.vvm.omtp.imap.ImapHelper.InitializingException;
 import com.android.phone.vvm.omtp.sync.OmtpVvmSourceManager;
 import com.android.phone.vvm.omtp.sync.VvmNetworkRequestCallback;
-
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 
@@ -142,17 +141,17 @@
 
         public fetchVoicemailNetworkRequestCallback(Context context,
                 PhoneAccountHandle phoneAccount) {
-            super(context, phoneAccount);
+            super(context, phoneAccount, VoicemailStatus.edit(context, phoneAccount));
         }
 
         @Override
         public void onAvailable(final Network network) {
             super.onAvailable(network);
-            fetchVoicemail(network);
+            fetchVoicemail(network, getVoicemailStatusEditor());
         }
     }
 
-    private void fetchVoicemail(final Network network) {
+    private void fetchVoicemail(final Network network, final VoicemailStatus.Editor status) {
         Executor executor = Executors.newCachedThreadPool();
         executor.execute(new Runnable() {
             @Override
@@ -161,7 +160,7 @@
                     while (mRetryCount > 0) {
                         VvmLog.i(TAG, "fetching voicemail, retry count=" + mRetryCount);
                         try (ImapHelper imapHelper = new ImapHelper(mContext, mPhoneAccount,
-                                network)) {
+                            network, status)) {
                             boolean success = imapHelper.fetchVoicemailPayload(
                                     new VoicemailFetchedCallback(mContext, mUri), mUid);
                             if (!success && mRetryCount > 0) {
diff --git a/src/com/android/phone/vvm/omtp/imap/ImapHelper.java b/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
index ce13323..d2df8de 100644
--- a/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
+++ b/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
@@ -72,6 +72,7 @@
     private final Context mContext;
     private final PhoneAccountHandle mPhoneAccount;
     private final Network mNetwork;
+    private final VoicemailStatus.Editor mStatus;
 
     VisualVoicemailPreferences mPrefs;
     private static final String PREF_KEY_QUOTA_OCCUPIED = "quota_occupied_";
@@ -89,17 +90,20 @@
         }
     }
 
-    public ImapHelper(Context context, PhoneAccountHandle phoneAccount, Network network)
+    public ImapHelper(Context context, PhoneAccountHandle phoneAccount, Network network,
+        VoicemailStatus.Editor status)
         throws InitializingException {
         this(context, new OmtpVvmCarrierConfigHelper(context,
-                PhoneUtils.getSubIdForPhoneAccountHandle(phoneAccount)), phoneAccount, network);
+            PhoneUtils.getSubIdForPhoneAccountHandle(phoneAccount)), phoneAccount, network, status);
     }
 
     public ImapHelper(Context context, OmtpVvmCarrierConfigHelper config,
-        PhoneAccountHandle phoneAccount, Network network) throws InitializingException {
+        PhoneAccountHandle phoneAccount, Network network, VoicemailStatus.Editor status)
+        throws InitializingException {
         mContext = context;
         mPhoneAccount = phoneAccount;
         mNetwork = network;
+        mStatus = status;
         mConfig = config;
         mPrefs = new VisualVoicemailPreferences(context,
                 phoneAccount);
@@ -123,7 +127,7 @@
             mImapStore = new ImapStore(
                     context, this, username, password, port, serverName, auth, network);
         } catch (NumberFormatException e) {
-            mConfig.handleEvent(OmtpEvents.DATA_INVALID_PORT);
+            handleEvent(OmtpEvents.DATA_INVALID_PORT);
             LogUtils.w(TAG, "Could not parse port number");
             throw new InitializingException("cannot initialize ImapHelper:" + e.toString());
         }
@@ -172,7 +176,7 @@
     }
 
     public void handleEvent(OmtpEvents event) {
-        mConfig.handleEvent(event);
+        mConfig.handleEvent(mStatus, event);
     }
 
     /**
diff --git a/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java b/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java
index 5f89471..e0b6359 100644
--- a/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java
+++ b/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java
@@ -21,6 +21,7 @@
 import android.os.Bundle;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.SmsManager;
+import com.android.phone.VoicemailStatus;
 import com.android.phone.vvm.omtp.ActivationTask;
 import com.android.phone.vvm.omtp.DefaultOmtpEventHandler;
 import com.android.phone.vvm.omtp.OmtpEvents;
@@ -52,7 +53,8 @@
     }
 
     public void startProvisioning(ActivationTask task, PhoneAccountHandle handle,
-            OmtpVvmCarrierConfigHelper config, StatusMessage message, Bundle data) {
+        OmtpVvmCarrierConfigHelper config, VoicemailStatus.Editor editor, StatusMessage message,
+        Bundle data) {
         // Do nothing
     }
 
@@ -79,8 +81,8 @@
     }
 
     public void handleEvent(Context context, OmtpVvmCarrierConfigHelper config,
-            OmtpEvents event) {
-        DefaultOmtpEventHandler.handleEvent(context, config, event);
+        VoicemailStatus.Editor status, OmtpEvents event) {
+        DefaultOmtpEventHandler.handleEvent(context, config, status, event);
     }
 
     /**
diff --git a/src/com/android/phone/vvm/omtp/protocol/Vvm3EventHandler.java b/src/com/android/phone/vvm/omtp/protocol/Vvm3EventHandler.java
index 3a3adb8..6fac708 100644
--- a/src/com/android/phone/vvm/omtp/protocol/Vvm3EventHandler.java
+++ b/src/com/android/phone/vvm/omtp/protocol/Vvm3EventHandler.java
@@ -18,7 +18,6 @@
 
 import android.annotation.IntDef;
 import android.content.Context;
-import android.telecom.PhoneAccountHandle;
 import android.util.Log;
 import com.android.phone.VoicemailStatus;
 import com.android.phone.settings.VoicemailChangePinActivity;
@@ -26,7 +25,6 @@
 import com.android.phone.vvm.omtp.OmtpEvents;
 import com.android.phone.vvm.omtp.OmtpEvents.Type;
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
-import com.android.phone.vvm.omtp.utils.PhoneAccountHandleConverter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -87,47 +85,46 @@
 
 
     public static void handleEvent(Context context, OmtpVvmCarrierConfigHelper config,
-            OmtpEvents event) {
+        VoicemailStatus.Editor status, OmtpEvents event) {
         boolean handled = false;
         switch (event.getType()) {
             case Type.CONFIGURATION:
-                handled = handleConfigurationEvent(context, config, event);
+                handled = handleConfigurationEvent(context, status, event);
                 break;
             case Type.DATA_CHANNEL:
-                handled = handleDataChannelEvent(context, config, event);
+                handled = handleDataChannelEvent(status, event);
                 break;
             case Type.NOTIFICATION_CHANNEL:
-                handled = handleNotificationChannelEvent(context, config, event);
+                handled = handleNotificationChannelEvent(status, event);
                 break;
             case Type.OTHER:
-                handled = handleOtherEvent(context, config, event);
+                handled = handleOtherEvent(status, event);
                 break;
             default:
                 com.android.services.telephony.Log
                         .wtf(TAG, "invalid event type " + event.getType() + " for " + event);
         }
         if (!handled) {
-            DefaultOmtpEventHandler.handleEvent(context, config, event);
+            DefaultOmtpEventHandler.handleEvent(context, config, status, event);
         }
     }
 
-    private static boolean handleConfigurationEvent(Context context,
-            OmtpVvmCarrierConfigHelper config, OmtpEvents event) {
+    private static boolean handleConfigurationEvent(Context context, VoicemailStatus.Editor status,
+        OmtpEvents event) {
         switch (event) {
             case CONFIG_REQUEST_STATUS_SUCCESS:
-                PhoneAccountHandle handle = PhoneAccountHandleConverter
-                        .fromSubId(config.getSubId());
-                if (!VoicemailChangePinActivity.isDefaultOldPinSet(context, handle)) {
+                if (!VoicemailChangePinActivity
+                    .isDefaultOldPinSet(context, status.getPhoneAccountHandle())) {
                     return false;
                 } else {
-                    postError(context, config, PIN_NOT_SET);
+                    postError(status, PIN_NOT_SET);
                 }
                 break;
             case CONFIG_DEFAULT_PIN_REPLACED:
-                postError(context, config, PIN_NOT_SET);
+                postError(status, PIN_NOT_SET);
                 break;
             case CONFIG_STATUS_SMS_TIME_OUT:
-                postError(context, config, STATUS_SMS_TIMEOUT);
+                postError(status, STATUS_SMS_TIMEOUT);
                 break;
             default:
                 return false;
@@ -135,50 +132,49 @@
         return true;
     }
 
-    private static boolean handleDataChannelEvent(Context context,
-            OmtpVvmCarrierConfigHelper config, OmtpEvents event) {
+    private static boolean handleDataChannelEvent(VoicemailStatus.Editor status, OmtpEvents event) {
         switch (event) {
             case DATA_NO_CONNECTION:
             case DATA_NO_CONNECTION_CELLULAR_REQUIRED:
             case DATA_ALL_SOCKET_CONNECTION_FAILED:
-                postError(context, config, VMS_NO_CELLULAR);
+                postError(status, VMS_NO_CELLULAR);
                 break;
             case DATA_SSL_INVALID_HOST_NAME:
             case DATA_CANNOT_ESTABLISH_SSL_SESSION:
             case DATA_IOE_ON_OPEN:
-                postError(context, config, VMS_TIMEOUT);
+                postError(status, VMS_TIMEOUT);
                 break;
             case DATA_CANNOT_RESOLVE_HOST_ON_NETWORK:
-                postError(context, config, VMS_DNS_FAILURE);
+                postError(status, VMS_DNS_FAILURE);
                 break;
             case DATA_BAD_IMAP_CREDENTIAL:
-                postError(context, config, IMAP_ERROR);
+                postError(status, IMAP_ERROR);
                 break;
             case DATA_AUTH_UNKNOWN_USER:
-                postError(context, config, UNKNOWN_USER);
+                postError(status, UNKNOWN_USER);
                 break;
             case DATA_AUTH_UNKNOWN_DEVICE:
-                postError(context, config, UNKNOWN_DEVICE);
+                postError(status, UNKNOWN_DEVICE);
                 break;
             case DATA_AUTH_INVALID_PASSWORD:
-                postError(context, config, INVALID_PASSWORD);
+                postError(status, INVALID_PASSWORD);
                 break;
             case DATA_AUTH_MAILBOX_NOT_INITIALIZED:
-                postError(context, config, MAILBOX_NOT_INITIALIZED);
+                postError(status, MAILBOX_NOT_INITIALIZED);
                 break;
             case DATA_AUTH_SERVICE_NOT_PROVISIONED:
-                postError(context, config, SERVICE_NOT_PROVISIONED);
+                postError(status, SERVICE_NOT_PROVISIONED);
                 break;
             case DATA_AUTH_SERVICE_NOT_ACTIVATED:
-                postError(context, config, SERVICE_NOT_ACTIVATED);
+                postError(status, SERVICE_NOT_ACTIVATED);
                 break;
             case DATA_AUTH_USER_IS_BLOCKED:
-                postError(context, config, USER_BLOCKED);
+                postError(status, USER_BLOCKED);
                 break;
             case DATA_REJECTED_SERVER_RESPONSE:
             case DATA_INVALID_INITIAL_SERVER_RESPONSE:
             case DATA_SSL_EXCEPTION:
-                postError(context, config, IMAP_ERROR);
+                postError(status, IMAP_ERROR);
                 break;
             default:
                 return false;
@@ -186,40 +182,40 @@
         return true;
     }
 
-    private static boolean handleNotificationChannelEvent(Context context,
-            OmtpVvmCarrierConfigHelper config, OmtpEvents event) {
+    private static boolean handleNotificationChannelEvent(VoicemailStatus.Editor status,
+        OmtpEvents event) {
         return false;
     }
 
-    private static boolean handleOtherEvent(Context context, OmtpVvmCarrierConfigHelper config,
+    private static boolean handleOtherEvent(VoicemailStatus.Editor status,
             OmtpEvents event) {
         switch (event) {
             case VVM3_NEW_USER_SETUP_FAILED:
-                postError(context, config, MAILBOX_NOT_INITIALIZED);
+                postError(status, MAILBOX_NOT_INITIALIZED);
                 break;
             case VVM3_VMG_DNS_FAILURE:
-                postError(context, config, VMG_DNS_FAILURE);
+                postError(status, VMG_DNS_FAILURE);
                 break;
             case VVM3_SPG_DNS_FAILURE:
-                postError(context, config, SPG_DNS_FAILURE);
+                postError(status, SPG_DNS_FAILURE);
                 break;
             case VVM3_VMG_CONNECTION_FAILED:
-                postError(context, config, VMG_NO_CELLULAR);
+                postError(status, VMG_NO_CELLULAR);
                 break;
             case VVM3_SPG_CONNECTION_FAILED:
-                postError(context, config, SPG_NO_CELLULAR);
+                postError(status, SPG_NO_CELLULAR);
                 break;
             case VVM3_VMG_TIMEOUT:
-                postError(context, config, VMG_TIMEOUT);
+                postError(status, VMG_TIMEOUT);
                 break;
             case VVM3_SUBSCRIBER_PROVISIONED:
-                postError(context, config, SERVICE_NOT_ACTIVATED);
+                postError(status, SERVICE_NOT_ACTIVATED);
                 break;
             case VVM3_SUBSCRIBER_BLOCKED:
-                postError(context, config, SUBSCRIBER_BLOCKED);
+                postError(status, SUBSCRIBER_BLOCKED);
                 break;
             case VVM3_SUBSCRIBER_UNKNOWN:
-                postError(context, config, SUBSCRIBER_UNKNOWN);
+                postError(status, SUBSCRIBER_UNKNOWN);
                 break;
             default:
                 return false;
@@ -227,10 +223,7 @@
         return true;
     }
 
-    private static void postError(Context context, OmtpVvmCarrierConfigHelper config,
-            @ErrorCode int errorCode) {
-        VoicemailStatus.Editor editor = VoicemailStatus.edit(context, config.getSubId());
-
+    private static void postError(VoicemailStatus.Editor editor, @ErrorCode int errorCode) {
         switch (errorCode) {
             case VMG_DNS_FAILURE:
             case SPG_DNS_FAILURE:
diff --git a/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java b/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java
index f5c65e2..39d2b34 100644
--- a/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java
+++ b/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java
@@ -23,6 +23,7 @@
 import android.telecom.PhoneAccountHandle;
 import android.telephony.SmsManager;
 import android.text.TextUtils;
+import com.android.phone.VoicemailStatus;
 import com.android.phone.common.mail.MessagingException;
 import com.android.phone.settings.VisualVoicemailSettingsUtil;
 import com.android.phone.settings.VoicemailChangePinActivity;
@@ -102,16 +103,17 @@
 
     @Override
     public void startProvisioning(ActivationTask task, PhoneAccountHandle phoneAccountHandle,
-            OmtpVvmCarrierConfigHelper config, StatusMessage message, Bundle data) {
+            OmtpVvmCarrierConfigHelper config, VoicemailStatus.Editor status, StatusMessage message,
+            Bundle data) {
         VvmLog.i(TAG, "start vvm3 provisioning");
         if (OmtpConstants.SUBSCRIBER_UNKNOWN.equals(message.getProvisioningStatus())) {
             VvmLog.i(TAG, "Provisioning status: Unknown");
             if (VVM3_UNKNOWN_SUBSCRIBER_CAN_SUBSCRIBE_RESPONSE_CODE
                     .equals(message.getReturnCode())) {
                 VvmLog.i(TAG, "Self provisioning available, subscribing");
-                new Vvm3Subscriber(task, phoneAccountHandle, config, data).subscribe();
+                new Vvm3Subscriber(task, phoneAccountHandle, config, status, data).subscribe();
             } else {
-                config.handleEvent(OmtpEvents.VVM3_SUBSCRIBER_UNKNOWN);
+                config.handleEvent(status, OmtpEvents.VVM3_SUBSCRIBER_UNKNOWN);
             }
         } else if (OmtpConstants.SUBSCRIBER_NEW.equals(message.getProvisioningStatus())) {
             VvmLog.i(TAG, "setting up new user");
@@ -120,14 +122,14 @@
                     new VisualVoicemailPreferences(config.getContext(), phoneAccountHandle);
             message.putStatus(prefs.edit()).apply();
 
-            startProvisionNewUser(task, phoneAccountHandle, config, message);
+            startProvisionNewUser(task, phoneAccountHandle, config, status, message);
         } else if (OmtpConstants.SUBSCRIBER_PROVISIONED.equals(message.getProvisioningStatus())) {
             VvmLog.i(TAG, "User provisioned but not activated, disabling VVM");
             VisualVoicemailSettingsUtil
                     .setEnabled(config.getContext(), phoneAccountHandle, false);
         } else if (OmtpConstants.SUBSCRIBER_BLOCKED.equals(message.getProvisioningStatus())) {
             VvmLog.i(TAG, "User blocked");
-            config.handleEvent(OmtpEvents.VVM3_SUBSCRIBER_BLOCKED);
+            config.handleEvent(status, OmtpEvents.VVM3_SUBSCRIBER_BLOCKED);
         }
     }
 
@@ -138,8 +140,9 @@
     }
 
     @Override
-    public void handleEvent(Context context, OmtpVvmCarrierConfigHelper config, OmtpEvents event) {
-        Vvm3EventHandler.handleEvent(context, config, event);
+    public void handleEvent(Context context, OmtpVvmCarrierConfigHelper config,
+            VoicemailStatus.Editor status, OmtpEvents event) {
+        Vvm3EventHandler.handleEvent(context, config, status, event);
     }
 
     @Override
@@ -183,13 +186,15 @@
     }
 
     private void startProvisionNewUser(ActivationTask task, PhoneAccountHandle phoneAccountHandle,
-            OmtpVvmCarrierConfigHelper config, StatusMessage message) {
-        try (NetworkWrapper wrapper = VvmNetworkRequest.getNetwork(config, phoneAccountHandle)) {
+            OmtpVvmCarrierConfigHelper config, VoicemailStatus.Editor status,
+            StatusMessage message) {
+        try (NetworkWrapper wrapper = VvmNetworkRequest
+                .getNetwork(config, phoneAccountHandle, status)) {
             Network network = wrapper.get();
 
             VvmLog.i(TAG, "new user: network available");
             try (ImapHelper helper = new ImapHelper(config.getContext(), phoneAccountHandle,
-                network)) {
+                    network, status)) {
                 // VVM3 has inconsistent error language code to OMTP. Just issue a raw command
                 // here.
                 // TODO(b/29082671): use LocaleList
@@ -213,12 +218,12 @@
                     config.requestStatus();
                 }
             } catch (InitializingException | MessagingException | IOException e) {
-                config.handleEvent(OmtpEvents.VVM3_NEW_USER_SETUP_FAILED);
+                config.handleEvent(status, OmtpEvents.VVM3_NEW_USER_SETUP_FAILED);
                 task.fail();
                 VvmLog.e(TAG, e.toString());
             }
         } catch (RequestFailedException e) {
-            config.handleEvent(OmtpEvents.DATA_NO_CONNECTION_CELLULAR_REQUIRED);
+            config.handleEvent(status, OmtpEvents.DATA_NO_CONNECTION_CELLULAR_REQUIRED);
             task.fail();
         }
 
diff --git a/src/com/android/phone/vvm/omtp/protocol/Vvm3Subscriber.java b/src/com/android/phone/vvm/omtp/protocol/Vvm3Subscriber.java
index 08e9352..ad00aa4 100644
--- a/src/com/android/phone/vvm/omtp/protocol/Vvm3Subscriber.java
+++ b/src/com/android/phone/vvm/omtp/protocol/Vvm3Subscriber.java
@@ -27,6 +27,7 @@
 import android.text.style.URLSpan;
 import android.util.ArrayMap;
 import com.android.phone.Assert;
+import com.android.phone.VoicemailStatus;
 import com.android.phone.vvm.omtp.ActivationTask;
 import com.android.phone.vvm.omtp.OmtpEvents;
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
@@ -113,6 +114,7 @@
     private final ActivationTask mTask;
     private final PhoneAccountHandle mHandle;
     private final OmtpVvmCarrierConfigHelper mHelper;
+    private final VoicemailStatus.Editor mStatus;
     private final Bundle mData;
 
     private final String mNumber;
@@ -137,11 +139,12 @@
 
     @WorkerThread
     public Vvm3Subscriber(ActivationTask task, PhoneAccountHandle handle,
-            OmtpVvmCarrierConfigHelper helper, Bundle data) {
+            OmtpVvmCarrierConfigHelper helper, VoicemailStatus.Editor status, Bundle data) {
         Assert.isNotMainThread();
         mTask = task;
         mHandle = handle;
         mHelper = helper;
+        mStatus = status;
         mData = data;
 
         // Assuming getLine1Number() will work with VVM3. For unprovisioned users the IMAP username
@@ -157,14 +160,14 @@
         // processSubscription() is called after network is available.
         VvmLog.i(TAG, "Subscribing");
 
-        try (NetworkWrapper wrapper = VvmNetworkRequest.getNetwork(mHelper, mHandle)) {
+        try (NetworkWrapper wrapper = VvmNetworkRequest.getNetwork(mHelper, mHandle, mStatus)) {
             Network network = wrapper.get();
             VvmLog.d(TAG, "provisioning: network available");
             mRequestQueue = Volley
                     .newRequestQueue(mHelper.getContext(), new NetworkSpecifiedHurlStack(network));
             processSubscription();
         } catch (RequestFailedException e) {
-            mHelper.handleEvent(OmtpEvents.VVM3_VMG_CONNECTION_FAILED);
+            mHelper.handleEvent(mStatus, OmtpEvents.VVM3_VMG_CONNECTION_FAILED);
             mTask.fail();
         }
     }
@@ -219,7 +222,7 @@
         try {
             return future.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
         } catch (InterruptedException | ExecutionException | TimeoutException e) {
-            mHelper.handleEvent(OmtpEvents.VVM3_SPG_CONNECTION_FAILED);
+            mHelper.handleEvent(mStatus, OmtpEvents.VVM3_SPG_CONNECTION_FAILED);
             throw new ProvisioningException(e.toString());
         }
     }
@@ -235,7 +238,7 @@
             // A new STATUS SMS will be sent after this request.
             future.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
         } catch (TimeoutException | ExecutionException | InterruptedException e) {
-            mHelper.handleEvent(OmtpEvents.VVM3_SPG_CONNECTION_FAILED);
+            mHelper.handleEvent(mStatus, OmtpEvents.VVM3_SPG_CONNECTION_FAILED);
             throw new ProvisioningException(e.toString());
         }
         // It could take very long for the STATUS SMS to return. Waiting for it is unreliable.
@@ -271,7 +274,7 @@
             }
             return response;
         } catch (InterruptedException | ExecutionException | TimeoutException e) {
-            mHelper.handleEvent(OmtpEvents.VVM3_VMG_CONNECTION_FAILED);
+            mHelper.handleEvent(mStatus, OmtpEvents.VVM3_VMG_CONNECTION_FAILED);
             throw new ProvisioningException(e.toString());
         }
     }
diff --git a/src/com/android/phone/vvm/omtp/scheduling/BaseTask.java b/src/com/android/phone/vvm/omtp/scheduling/BaseTask.java
index 8b3e88e..273d2d3 100644
--- a/src/com/android/phone/vvm/omtp/scheduling/BaseTask.java
+++ b/src/com/android/phone/vvm/omtp/scheduling/BaseTask.java
@@ -23,10 +23,8 @@
 import android.content.Intent;
 import android.os.SystemClock;
 import android.support.annotation.NonNull;
-
 import com.android.phone.Assert;
 import com.android.phone.NeededForTesting;
-
 import java.util.ArrayList;
 import java.util.List;
 
@@ -167,6 +165,7 @@
     }
 
     @Override
+    @CallSuper
     public void onCompleted() {
         if (mHasFailed) {
             for (Policy policy : mPolicies) {
diff --git a/src/com/android/phone/vvm/omtp/scheduling/RetryPolicy.java b/src/com/android/phone/vvm/omtp/scheduling/RetryPolicy.java
index 3c2274f..bd0c75e 100644
--- a/src/com/android/phone/vvm/omtp/scheduling/RetryPolicy.java
+++ b/src/com/android/phone/vvm/omtp/scheduling/RetryPolicy.java
@@ -17,7 +17,7 @@
 package com.android.phone.vvm.omtp.scheduling;
 
 import android.content.Intent;
-
+import com.android.phone.VoicemailStatus;
 import com.android.phone.vvm.omtp.VvmLog;
 
 /**
@@ -38,11 +38,26 @@
     private int mRetryCount;
     private boolean mFailed;
 
+    private VoicemailStatus.DeferredEditor mVoicemailStatusEditor;
+
     public RetryPolicy(int retryLimit, int retryDelayMillis) {
         mRetryLimit = retryLimit;
         mRetryDelayMillis = retryDelayMillis;
     }
 
+    private boolean hasMoreRetries() {
+        return mRetryCount < mRetryLimit;
+    }
+
+    /**
+     * Error status should only be set if retries has exhausted or the task is successful. Status
+     * writes to this editor will be deferred until the task has ended, and will only be committed
+     * if the task is successful or there are no retries left.
+     */
+    public VoicemailStatus.Editor getVoicemailStatusEditor() {
+        return mVoicemailStatusEditor;
+    }
+
     @Override
     public void onCreate(BaseTask task, Intent intent, int flags, int startId) {
         mTask = task;
@@ -52,6 +67,8 @@
                     + mRetryDelayMillis);
             mTask.setExecutionTime(mTask.getTimeMillis() + mRetryDelayMillis);
         }
+        mVoicemailStatusEditor = VoicemailStatus.deferredEdit(task.getContext(),
+                task.getSubId());
     }
 
     @Override
@@ -61,14 +78,18 @@
 
     @Override
     public void onCompleted() {
-        if (!mFailed) {
+        if (!mFailed || !hasMoreRetries()) {
+            if (!mFailed) {
+                VvmLog.d(TAG, mTask.toString() + " completed successfully");
+            }
+            if (!hasMoreRetries()) {
+                VvmLog.d(TAG, "Retry limit for " + mTask + " reached");
+            }
+            VvmLog.i(TAG, "committing deferred status: " + mVoicemailStatusEditor.getValues());
+            mVoicemailStatusEditor.deferredApply();
             return;
         }
-        if (mRetryCount >= mRetryLimit) {
-            VvmLog.d(TAG, "Retry limit for " + mTask + " reached");
-            return;
-        }
-
+        VvmLog.i(TAG, "discarding deferred status: " + mVoicemailStatusEditor.getValues());
         Intent intent = mTask.createRestartIntent();
         intent.putExtra(EXTRA_RETRY_COUNT, mRetryCount + 1);
 
diff --git a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSourceManager.java b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSourceManager.java
index 1972ca6..ac4ba0a 100644
--- a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSourceManager.java
+++ b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSourceManager.java
@@ -20,14 +20,13 @@
 import android.telephony.PhoneStateListener;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
-
 import com.android.internal.telephony.Phone;
 import com.android.phone.PhoneUtils;
+import com.android.phone.VoicemailStatus;
 import com.android.phone.vvm.omtp.OmtpEvents;
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
 import com.android.phone.vvm.omtp.VvmPhoneStateListener;
 import com.android.phone.vvm.omtp.utils.PhoneAccountHandleConverter;
-
 import java.util.Collections;
 import java.util.Map;
 import java.util.Set;
@@ -108,7 +107,8 @@
 
     public void removeSource(PhoneAccountHandle phoneAccount) {
         new OmtpVvmCarrierConfigHelper(mContext, PhoneAccountHandleConverter.toSubId(phoneAccount))
-                .handleEvent(OmtpEvents.OTHER_SOURCE_REMOVED);
+            .handleEvent(VoicemailStatus.edit(mContext, phoneAccount),
+                OmtpEvents.OTHER_SOURCE_REMOVED);
         removePhoneStateListener(phoneAccount);
         mActiveVvmSources.remove(phoneAccount);
     }
diff --git a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
index 157f51f..2dc27b2 100644
--- a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
+++ b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
@@ -24,6 +24,7 @@
 import android.telecom.Voicemail;
 import android.text.TextUtils;
 import com.android.phone.PhoneUtils;
+import com.android.phone.VoicemailStatus;
 import com.android.phone.settings.VisualVoicemailSettingsUtil;
 import com.android.phone.vvm.omtp.ActivationTask;
 import com.android.phone.vvm.omtp.OmtpEvents;
@@ -81,19 +82,19 @@
     }
 
     public void sync(BaseTask task, String action, PhoneAccountHandle phoneAccount,
-            Voicemail voicemail) {
+            Voicemail voicemail, VoicemailStatus.Editor status) {
         VvmLog.log(TAG, "Sync requested: " + action +
                 " for all accounts: " + String.valueOf(phoneAccount == null));
         if (phoneAccount != null) {
             VvmLog.v(TAG, "Sync requested: " + action + " - for account: " + phoneAccount);
-            setupAndSendRequest(task, phoneAccount, voicemail, action);
+            setupAndSendRequest(task, phoneAccount, voicemail, action, status);
         } else {
             VvmLog.v(TAG, "Sync requested: " + action + " - for all accounts");
             OmtpVvmSourceManager vvmSourceManager =
                     OmtpVvmSourceManager.getInstance(mContext);
             Set<PhoneAccountHandle> sources = vvmSourceManager.getOmtpVvmSources();
             for (PhoneAccountHandle source : sources) {
-                setupAndSendRequest(task, source, null, action);
+                setupAndSendRequest(task, source, null, action, status);
             }
             activateUnactivatedAccounts();
         }
@@ -115,7 +116,7 @@
     }
 
     private void setupAndSendRequest(BaseTask task, PhoneAccountHandle phoneAccount,
-            Voicemail voicemail, String action) {
+            Voicemail voicemail, String action, VoicemailStatus.Editor status) {
         if (!VisualVoicemailSettingsUtil.isEnabled(mContext, phoneAccount)) {
             VvmLog.v(TAG, "Sync requested for disabled account");
             return;
@@ -127,23 +128,27 @@
         }
 
         OmtpVvmCarrierConfigHelper config = new OmtpVvmCarrierConfigHelper(mContext, subId);
-        config.handleEvent(OmtpEvents.DATA_IMAP_OPERATION_STARTED);
-        try (NetworkWrapper network = VvmNetworkRequest.getNetwork(config, phoneAccount)) {
+        // DATA_IMAP_OPERATION_STARTED posting should not be deferred. This event clears all data
+        // channel errors, which should happen when the task starts, not when it ends. It is the
+        // "Sync in progress..." status.
+        config.handleEvent(VoicemailStatus.edit(mContext, phoneAccount),
+                OmtpEvents.DATA_IMAP_OPERATION_STARTED);
+        try (NetworkWrapper network = VvmNetworkRequest.getNetwork(config, phoneAccount, status)) {
             if (network == null) {
                 VvmLog.e(TAG, "unable to acquire network");
                 task.fail();
                 return;
             }
-            doSync(task, network.get(), phoneAccount, voicemail, action);
+            doSync(task, network.get(), phoneAccount, voicemail, action, status);
         } catch (RequestFailedException e) {
-            config.handleEvent(OmtpEvents.DATA_NO_CONNECTION_CELLULAR_REQUIRED);
+            config.handleEvent(status, OmtpEvents.DATA_NO_CONNECTION_CELLULAR_REQUIRED);
             task.fail();
         }
     }
 
     private void doSync(BaseTask task, Network network, PhoneAccountHandle phoneAccount,
-            Voicemail voicemail, String action) {
-        try(ImapHelper imapHelper = new ImapHelper(mContext, phoneAccount, network)) {
+            Voicemail voicemail, String action, VoicemailStatus.Editor status) {
+        try (ImapHelper imapHelper = new ImapHelper(mContext, phoneAccount, network, status)) {
             boolean success;
             if (voicemail == null) {
                 success = syncAll(action, imapHelper, phoneAccount);
diff --git a/src/com/android/phone/vvm/omtp/sync/SyncOneTask.java b/src/com/android/phone/vvm/omtp/sync/SyncOneTask.java
index 165f043..510efc7 100644
--- a/src/com/android/phone/vvm/omtp/sync/SyncOneTask.java
+++ b/src/com/android/phone/vvm/omtp/sync/SyncOneTask.java
@@ -20,7 +20,7 @@
 import android.content.Intent;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.Voicemail;
-
+import com.android.phone.VoicemailStatus;
 import com.android.phone.vvm.omtp.scheduling.BaseTask;
 import com.android.phone.vvm.omtp.scheduling.RetryPolicy;
 import com.android.phone.vvm.omtp.utils.PhoneAccountHandleConverter;
@@ -67,7 +67,8 @@
     @Override
     public void onExecuteInBackgroundThread() {
         OmtpVvmSyncService service = new OmtpVvmSyncService(getContext());
-        service.sync(this, mSyncType, mPhone, mVoicemail);
+        service.sync(this, mSyncType, mPhone, mVoicemail,
+                VoicemailStatus.edit(getContext(), mPhone));
     }
 
     @Override
diff --git a/src/com/android/phone/vvm/omtp/sync/SyncTask.java b/src/com/android/phone/vvm/omtp/sync/SyncTask.java
index 0d91c5c..7374ee6 100644
--- a/src/com/android/phone/vvm/omtp/sync/SyncTask.java
+++ b/src/com/android/phone/vvm/omtp/sync/SyncTask.java
@@ -37,6 +37,8 @@
     private static final String EXTRA_PHONE_ACCOUNT_HANDLE = "extra_phone_account_handle";
     private static final String EXTRA_SYNC_TYPE = "extra_sync_type";
 
+    private final RetryPolicy mRetryPolicy;
+
     private PhoneAccountHandle mPhone;
     private String mSyncType;
 
@@ -50,7 +52,8 @@
 
     public SyncTask() {
         super(TASK_SYNC);
-        addPolicy(new RetryPolicy(RETRY_TIMES, RETRY_INTERVAL_MILLIS));
+        mRetryPolicy = new RetryPolicy(RETRY_TIMES, RETRY_INTERVAL_MILLIS);
+        addPolicy(mRetryPolicy);
         addPolicy(new MinimalIntervalPolicy(MINIMAL_INTERVAL_MILLIS));
     }
 
@@ -63,7 +66,7 @@
     @Override
     public void onExecuteInBackgroundThread() {
         OmtpVvmSyncService service = new OmtpVvmSyncService(getContext());
-        service.sync(this, mSyncType, mPhone, null);
+        service.sync(this, mSyncType, mPhone, null, mRetryPolicy.getVoicemailStatusEditor());
     }
 
     @Override
diff --git a/src/com/android/phone/vvm/omtp/sync/UploadTask.java b/src/com/android/phone/vvm/omtp/sync/UploadTask.java
index afdee58..7945c08 100644
--- a/src/com/android/phone/vvm/omtp/sync/UploadTask.java
+++ b/src/com/android/phone/vvm/omtp/sync/UploadTask.java
@@ -18,7 +18,7 @@
 
 import android.content.Context;
 import android.content.Intent;
-
+import com.android.phone.VoicemailStatus;
 import com.android.phone.vvm.omtp.scheduling.BaseTask;
 import com.android.phone.vvm.omtp.scheduling.PostponePolicy;
 
@@ -44,6 +44,7 @@
     @Override
     public void onExecuteInBackgroundThread() {
         OmtpVvmSyncService service = new OmtpVvmSyncService(getContext());
-        service.sync(this, OmtpVvmSyncService.SYNC_UPLOAD_ONLY, null, null);
+        service.sync(this, OmtpVvmSyncService.SYNC_UPLOAD_ONLY, null, null,
+                VoicemailStatus.edit(getContext(), getSubId()));
     }
 }
diff --git a/src/com/android/phone/vvm/omtp/sync/VvmNetworkRequest.java b/src/com/android/phone/vvm/omtp/sync/VvmNetworkRequest.java
index 798950d..3bdb45b 100644
--- a/src/com/android/phone/vvm/omtp/sync/VvmNetworkRequest.java
+++ b/src/com/android/phone/vvm/omtp/sync/VvmNetworkRequest.java
@@ -19,6 +19,7 @@
 import android.net.Network;
 import android.support.annotation.NonNull;
 import android.telecom.PhoneAccountHandle;
+import com.android.phone.VoicemailStatus;
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
 import com.android.phone.vvm.omtp.VvmLog;
 import java.io.Closeable;
@@ -67,8 +68,9 @@
 
     @NonNull
     public static NetworkWrapper getNetwork(OmtpVvmCarrierConfigHelper config,
-            PhoneAccountHandle handle) throws RequestFailedException {
-        FutureNetworkRequestCallback callback = new FutureNetworkRequestCallback(config, handle);
+        PhoneAccountHandle handle, VoicemailStatus.Editor status) throws RequestFailedException {
+        FutureNetworkRequestCallback callback = new FutureNetworkRequestCallback(config, handle,
+            status);
         callback.requestNetwork();
         try {
             return callback.getFuture().get();
@@ -88,8 +90,8 @@
         private final CompletableFuture<NetworkWrapper> mFuture = new CompletableFuture<>();
 
         public FutureNetworkRequestCallback(OmtpVvmCarrierConfigHelper config,
-                PhoneAccountHandle phoneAccount) {
-            super(config, phoneAccount);
+            PhoneAccountHandle phoneAccount, VoicemailStatus.Editor status) {
+            super(config, phoneAccount, status);
         }
 
         public Future<NetworkWrapper> getFuture() {
diff --git a/src/com/android/phone/vvm/omtp/sync/VvmNetworkRequestCallback.java b/src/com/android/phone/vvm/omtp/sync/VvmNetworkRequestCallback.java
index 11526ce..787fdcd 100644
--- a/src/com/android/phone/vvm/omtp/sync/VvmNetworkRequestCallback.java
+++ b/src/com/android/phone/vvm/omtp/sync/VvmNetworkRequestCallback.java
@@ -24,8 +24,8 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.telecom.PhoneAccountHandle;
-
 import com.android.phone.PhoneUtils;
+import com.android.phone.VoicemailStatus;
 import com.android.phone.vvm.omtp.OmtpEvents;
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
 import com.android.phone.vvm.omtp.VvmLog;
@@ -50,26 +50,34 @@
     private ConnectivityManager mConnectivityManager;
     private final OmtpVvmCarrierConfigHelper mCarrierConfigHelper;
     private final int mSubId;
+    private final VoicemailStatus.Editor mStatus;
     private boolean mRequestSent = false;
     private boolean mResultReceived = false;
 
-    public VvmNetworkRequestCallback(Context context, PhoneAccountHandle phoneAccount) {
+    public VvmNetworkRequestCallback(Context context, PhoneAccountHandle phoneAccount,
+        VoicemailStatus.Editor status) {
         mContext = context;
         mPhoneAccount = phoneAccount;
         mSubId = PhoneUtils.getSubIdForPhoneAccountHandle(phoneAccount);
+        mStatus = status;
         mCarrierConfigHelper = new OmtpVvmCarrierConfigHelper(context, mSubId);
         mNetworkRequest = createNetworkRequest();
     }
 
     public VvmNetworkRequestCallback(OmtpVvmCarrierConfigHelper config,
-            PhoneAccountHandle phoneAccount) {
+        PhoneAccountHandle phoneAccount, VoicemailStatus.Editor status) {
         mContext = config.getContext();
         mPhoneAccount = phoneAccount;
         mSubId = config.getSubId();
+        mStatus = status;
         mCarrierConfigHelper = config;
         mNetworkRequest = createNetworkRequest();
     }
 
+    public VoicemailStatus.Editor getVoicemailStatusEditor() {
+        return mStatus;
+    }
+
     /**
      * @return NetworkRequest for a proper transport type. Use only cellular network if the carrier
      * requires it. Otherwise use whatever available.
@@ -154,9 +162,10 @@
     public void onFailed(String reason) {
         VvmLog.d(TAG, "onFailed: " + reason);
         if (mCarrierConfigHelper.isCellularDataRequired()) {
-            mCarrierConfigHelper.handleEvent(OmtpEvents.DATA_NO_CONNECTION_CELLULAR_REQUIRED);
+            mCarrierConfigHelper
+                .handleEvent(mStatus, OmtpEvents.DATA_NO_CONNECTION_CELLULAR_REQUIRED);
         } else {
-            mCarrierConfigHelper.handleEvent(OmtpEvents.DATA_NO_CONNECTION);
+            mCarrierConfigHelper.handleEvent(mStatus, OmtpEvents.DATA_NO_CONNECTION);
         }
         releaseNetwork();
     }