Add Error handling for VVM3

This CL requires the prebuilt dialer to be updated, else PIN not being
set will be considered an error and the dialer will not show the
voicemail tab. b/29577838 will disable VVM3 by default so dialer support
is ensured.

Bug:29082418
Bug:28968317
Change-Id: Iaad58711853ce350916ce3f49db69ef8f92156e4
diff --git a/src/com/android/phone/common/mail/store/ImapConnection.java b/src/com/android/phone/common/mail/store/ImapConnection.java
index de40f2c..0360e3e 100644
--- a/src/com/android/phone/common/mail/store/ImapConnection.java
+++ b/src/com/android/phone/common/mail/store/ImapConnection.java
@@ -15,7 +15,6 @@
  */
 package com.android.phone.common.mail.store;
 
-import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Base64;
 
@@ -180,18 +179,48 @@
             }
         } catch (ImapException ie) {
             LogUtils.d(TAG, "ImapException", ie);
-            final String status = ie.getStatus();
-            final String code = ie.getResponseCode();
-            final String alertText = ie.getAlertText();
+            String status = ie.getStatus();
+            String statusMessage = ie.getStatusMessage();
+            String alertText = ie.getAlertText();
 
-            // if the response code indicates expired or bad credentials, throw a special exception
-            if (ImapConstants.AUTHENTICATIONFAILED.equals(code) ||
-                    ImapConstants.EXPIRED.equals(code) ||
-                    (ImapConstants.NO.equals(status) && TextUtils.isEmpty(code))) {
-                mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_BAD_IMAP_CREDENTIAL);
+            if (ImapConstants.NO.equals(status)) {
+                switch (statusMessage) {
+                    case ImapConstants.NO_UNKNOWN_USER:
+                        mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_AUTH_UNKNOWN_USER);
+                        break;
+                    case ImapConstants.NO_UNKNOWN_CLIENT:
+                        mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_AUTH_UNKNOWN_DEVICE);
+                        break;
+                    case ImapConstants.NO_INVALID_PASSWORD:
+                        mImapStore.getImapHelper()
+                                .handleEvent(OmtpEvents.DATA_AUTH_INVALID_PASSWORD);
+                        break;
+                    case ImapConstants.NO_MAILBOX_NOT_INITIALIZED:
+                        mImapStore.getImapHelper()
+                                .handleEvent(OmtpEvents.DATA_AUTH_MAILBOX_NOT_INITIALIZED);
+                        break;
+                    case ImapConstants.NO_SERVICE_IS_NOT_PROVISIONED:
+                        mImapStore.getImapHelper()
+                                .handleEvent(OmtpEvents.DATA_AUTH_SERVICE_NOT_PROVISIONED);
+                        break;
+                    case ImapConstants.NO_SERVICE_IS_NOT_ACTIVATED:
+                        mImapStore.getImapHelper()
+                                .handleEvent(OmtpEvents.DATA_AUTH_SERVICE_NOT_ACTIVATED);
+                        break;
+                    case ImapConstants.NO_USER_IS_BLOCKED:
+                        mImapStore.getImapHelper()
+                                .handleEvent(OmtpEvents.DATA_AUTH_USER_IS_BLOCKED);
+                        break;
+                    case ImapConstants.NO_APPLICATION_ERROR:
+                        mImapStore.getImapHelper()
+                                .handleEvent(OmtpEvents.DATA_REJECTED_SERVER_RESPONSE);
+                    default:
+                        mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_BAD_IMAP_CREDENTIAL);
+                }
                 throw new AuthenticationFailedException(alertText, ie);
             }
 
+            mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_REJECTED_SERVER_RESPONSE);
             throw new MessagingException(alertText, ie);
         }
     }
@@ -359,16 +388,11 @@
         if (!(response.isOk() || response.isContinuationRequest())) {
             final String toString = response.toString();
             final String status = response.getStatusOrEmpty().getString();
+            final String statusMessage = response.getStatusResponseTextOrEmpty().getString();
             final String alert = response.getAlertTextOrEmpty().getString();
             final String responseCode = response.getResponseCodeOrEmpty().getString();
             destroyResponses();
-            mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_REJECTED_SERVER_RESPONSE);
-            // if the response code indicates an error occurred within the server, indicate that
-            if (ImapConstants.UNAVAILABLE.equals(responseCode)) {
-
-                throw new MessagingException(MessagingException.SERVER_ERROR, alert);
-            }
-            throw new ImapException(toString, status, alert, responseCode);
+            throw new ImapException(toString, status, statusMessage, alert, responseCode);
         }
         return responses;
     }
diff --git a/src/com/android/phone/common/mail/store/ImapStore.java b/src/com/android/phone/common/mail/store/ImapStore.java
index c8095e5..179d0f2 100644
--- a/src/com/android/phone/common/mail/store/ImapStore.java
+++ b/src/com/android/phone/common/mail/store/ImapStore.java
@@ -130,13 +130,15 @@
         private static final long serialVersionUID = 1L;
 
         private final String mStatus;
+        private final String mStatusMessage;
         private final String mAlertText;
         private final String mResponseCode;
 
-        public ImapException(String message, String status, String alertText,
+        public ImapException(String message, String status, String statusMessage, String alertText,
                 String responseCode) {
             super(message);
             mStatus = status;
+            mStatusMessage = statusMessage;
             mAlertText = alertText;
             mResponseCode = responseCode;
         }
@@ -145,6 +147,10 @@
             return mStatus;
         }
 
+        public String getStatusMessage() {
+            return mStatusMessage;
+        }
+
         public String getAlertText() {
             return mAlertText;
         }
diff --git a/src/com/android/phone/common/mail/store/imap/ImapConstants.java b/src/com/android/phone/common/mail/store/imap/ImapConstants.java
index 9e6e247..a2eab13 100644
--- a/src/com/android/phone/common/mail/store/imap/ImapConstants.java
+++ b/src/com/android/phone/common/mail/store/imap/ImapConstants.java
@@ -98,6 +98,32 @@
     public static final String NIL = "NIL";
 
     /**
+     * NO responses
+     */
+    public static final String NO_COMMAND_NOT_ALLOWED = "command not allowed";
+    public static final String NO_RESERVATION_FAILED = "reservation failed";
+    public static final String NO_APPLICATION_ERROR = "application error";
+    public static final String NO_INVALID_PARAMETER = "invalid parameter";
+    public static final String NO_INVALID_COMMAND = "invalid command";
+    public static final String NO_UNKNOWN_COMMAND = "unknown command";
+    // AUTHENTICATE
+    // The subscriber can not be located in the system.
+    public static final String NO_UNKNOWN_USER = "unknown user";
+    // The Client Type or Protocol Version is unknown.
+    public static final String NO_UNKNOWN_CLIENT = "unknown client";
+    // The password received from the client does not match the password defined in the subscriber's profile.
+    public static final String NO_INVALID_PASSWORD = "invalid password";
+    // The subscriber's mailbox has not yet been initialised via the TUI
+    public static final String NO_MAILBOX_NOT_INITIALIZED = "mailbox not initialized";
+    // The subscriber has not been provisioned for the VVM service.
+    public static final String NO_SERVICE_IS_NOT_PROVISIONED =
+            "service is not provisioned";
+    // The subscriber is provisioned for the VVM service but the VVM service is currently not active
+    public static final String NO_SERVICE_IS_NOT_ACTIVATED = "service is not activated";
+    // The Voice Mail Blocked flag in the subscriber's profile is set to YES.
+    public static final String NO_USER_IS_BLOCKED = "user is blocked";
+
+    /**
      * extensions
      */
     public static final String GETQUOTA = "GETQUOTA";
@@ -105,11 +131,6 @@
     public static final String QUOTAROOT = "QUOTAROOT";
     public static final String QUOTA = "QUOTA";
 
-    /** response codes within IMAP responses */
-    public static final String EXPIRED = "EXPIRED";
-    public static final String AUTHENTICATIONFAILED = "AUTHENTICATIONFAILED";
-    public static final String UNAVAILABLE = "UNAVAILABLE";
-
     /**
      * capabilities
      */
diff --git a/src/com/android/phone/settings/VoicemailChangePinDialogPreference.java b/src/com/android/phone/settings/VoicemailChangePinDialogPreference.java
index d960dc4..e4230ff 100644
--- a/src/com/android/phone/settings/VoicemailChangePinDialogPreference.java
+++ b/src/com/android/phone/settings/VoicemailChangePinDialogPreference.java
@@ -35,6 +35,7 @@
 import com.android.phone.common.mail.MessagingException;
 import com.android.phone.vvm.omtp.OmtpConstants;
 import com.android.phone.vvm.omtp.OmtpConstants.ChangePinResult;
+import com.android.phone.vvm.omtp.OmtpEvents;
 import com.android.phone.vvm.omtp.imap.ImapHelper;
 import com.android.phone.vvm.omtp.sync.VvmNetworkRequestCallback;
 
@@ -192,6 +193,7 @@
                     // Wipe the default old PIN so the old PIN input box will be shown to the user
                     // on the next time.
                     setDefaultOldPIN(mContext, mPhoneAccountHandle, null);
+                    helper.handleEvent(OmtpEvents.CONFIG_PIN_SET);
                 }
             } catch (MessagingException e) {
                 finishPinChange();
diff --git a/src/com/android/phone/vvm/omtp/DefaultOmtpEventHandler.java b/src/com/android/phone/vvm/omtp/DefaultOmtpEventHandler.java
index 806ccd5..6837bf5 100644
--- a/src/com/android/phone/vvm/omtp/DefaultOmtpEventHandler.java
+++ b/src/com/android/phone/vvm/omtp/DefaultOmtpEventHandler.java
@@ -50,6 +50,7 @@
             OmtpEvents event) {
         switch (event) {
             case CONFIG_REQUEST_STATUS_SUCCESS:
+            case CONFIG_PIN_SET:
                 VoicemailStatus.edit(context, subId)
                         .setConfigurationState(VoicemailContract.Status.CONFIGURATION_STATE_OK)
                         .setNotificationChannelState(Status.NOTIFICATION_CHANNEL_STATE_OK)
@@ -102,6 +103,13 @@
                         .apply();
                 break;
             case DATA_BAD_IMAP_CREDENTIAL:
+            case DATA_AUTH_UNKNOWN_USER:
+            case DATA_AUTH_UNKNOWN_DEVICE:
+            case DATA_AUTH_INVALID_PASSWORD:
+            case DATA_AUTH_MAILBOX_NOT_INITIALIZED:
+            case DATA_AUTH_SERVICE_NOT_PROVISIONED:
+            case DATA_AUTH_SERVICE_NOT_ACTIVATED:
+            case DATA_AUTH_USER_IS_BLOCKED:
                 VoicemailStatus.edit(context, subId)
                         .setDataChannelState(
                                 VoicemailContract.Status.DATA_CHANNEL_STATE_BAD_CONFIGURATION)
diff --git a/src/com/android/phone/vvm/omtp/OmtpEvents.java b/src/com/android/phone/vvm/omtp/OmtpEvents.java
index 6de692e..f42db72 100644
--- a/src/com/android/phone/vvm/omtp/OmtpEvents.java
+++ b/src/com/android/phone/vvm/omtp/OmtpEvents.java
@@ -31,43 +31,55 @@
     // Configuration State
     CONFIG_REQUEST_STATUS_SUCCESS(Type.CONFIGURATION, true),
 
+    CONFIG_PIN_SET(Type.CONFIGURATION, true),
+    // The voicemail PIN is replaced with a generated PIN, user should change it.
+    CONFIG_DEFAULT_PIN_REPLACED(Type.CONFIGURATION, true),
+
     // Data channel State
 
     // Successfully downloaded/uploaded data from the server, which means the data channel is clear.
     DATA_IMAP_OPERATION_COMPLETED(Type.DATA_CHANNEL, true),
-
     // The port provided in the STATUS SMS is invalid.
-    DATA_INVALID_PORT(Type.DATA_CHANNEL, false),
+    DATA_INVALID_PORT(Type.DATA_CHANNEL),
     // No connection to the internet, and the carrier requires cellular data
-    DATA_NO_CONNECTION_CELLULAR_REQUIRED(Type.DATA_CHANNEL, false),
+    DATA_NO_CONNECTION_CELLULAR_REQUIRED(Type.DATA_CHANNEL),
     // No connection to the internet.
-    DATA_NO_CONNECTION(Type.DATA_CHANNEL, false),
+    DATA_NO_CONNECTION(Type.DATA_CHANNEL),
     // Address lookup for the server hostname failed. DNS error?
-    DATA_CANNOT_RESOLVE_HOST_ON_NETWORK(Type.DATA_CHANNEL, false),
+    DATA_CANNOT_RESOLVE_HOST_ON_NETWORK(Type.DATA_CHANNEL),
     // All destination address that resolves to the server hostname are rejected or timed out
-    DATA_ALL_SOCKET_CONNECTION_FAILED(Type.DATA_CHANNEL, false),
+    DATA_ALL_SOCKET_CONNECTION_FAILED(Type.DATA_CHANNEL),
     // Failed to establish SSL with the server, either with a direct SSL connection or by
     // STARTTLS command
-    DATA_CANNOT_ESTABLISH_SSL_SESSION(Type.DATA_CHANNEL, false),
+    DATA_CANNOT_ESTABLISH_SSL_SESSION(Type.DATA_CHANNEL),
     // Identity of the server cannot be verified.
-    DATA_SSL_INVALID_HOST_NAME(Type.DATA_CHANNEL, false),
+    DATA_SSL_INVALID_HOST_NAME(Type.DATA_CHANNEL),
     // The server rejected our username/password
-    DATA_BAD_IMAP_CREDENTIAL(Type.DATA_CHANNEL, false),
+    DATA_BAD_IMAP_CREDENTIAL(Type.DATA_CHANNEL),
+
+    DATA_AUTH_UNKNOWN_USER(Type.DATA_CHANNEL),
+    DATA_AUTH_UNKNOWN_DEVICE(Type.DATA_CHANNEL),
+    DATA_AUTH_INVALID_PASSWORD(Type.DATA_CHANNEL),
+    DATA_AUTH_MAILBOX_NOT_INITIALIZED(Type.DATA_CHANNEL),
+    DATA_AUTH_SERVICE_NOT_PROVISIONED(Type.DATA_CHANNEL),
+    DATA_AUTH_SERVICE_NOT_ACTIVATED(Type.DATA_CHANNEL),
+    DATA_AUTH_USER_IS_BLOCKED(Type.DATA_CHANNEL),
+
     // A command to the server didn't result with an "OK" or continuation request
-    DATA_REJECTED_SERVER_RESPONSE(Type.DATA_CHANNEL, false),
+    DATA_REJECTED_SERVER_RESPONSE(Type.DATA_CHANNEL),
     // The server did not greet us with a "OK", possibly not a IMAP server.
-    DATA_INVALID_INITIAL_SERVER_RESPONSE(Type.DATA_CHANNEL, false),
+    DATA_INVALID_INITIAL_SERVER_RESPONSE(Type.DATA_CHANNEL),
     // An IOException occurred while trying to open an ImapConnection
     // TODO: reduce scope
-    DATA_IOE_ON_OPEN(Type.DATA_CHANNEL, false),
+    DATA_IOE_ON_OPEN(Type.DATA_CHANNEL),
     // The SELECT command on a mailbox is rejected
-    DATA_MAILBOX_OPEN_FAILED(Type.DATA_CHANNEL, false),
+    DATA_MAILBOX_OPEN_FAILED(Type.DATA_CHANNEL),
     // An IOException has occurred
     // TODO: reduce scope
-    DATA_GENERIC_IMAP_IOE(Type.DATA_CHANNEL, false),
+    DATA_GENERIC_IMAP_IOE(Type.DATA_CHANNEL),
     // An SslException has occurred while opening an ImapConnection
     // TODO: reduce scope
-    DATA_SSL_EXCEPTION(Type.DATA_CHANNEL, false),
+    DATA_SSL_EXCEPTION(Type.DATA_CHANNEL),
 
     // Notification Channel
 
@@ -78,8 +90,20 @@
 
 
     // Other
-    OTHER_SOURCE_REMOVED(Type.OTHER, false);
+    OTHER_SOURCE_REMOVED(Type.OTHER, false),
 
+    // VVM3
+    VVM3_NEW_USER_SETUP_FAILED,
+    // Table 4. client internal error handling
+    VVM3_VMG_DNS_FAILURE,
+    VVM3_SPG_DNS_FAILURE,
+    VVM3_VMG_CONNECTION_FAILED,
+    VVM3_SPG_CONNECTION_FAILED,
+    VVM3_VMG_TIMEOUT,
+    VVM3_STATUS_SMS_TIMEOUT,
+
+    VVM3_SUBSCRIBER_PROVISIONED,
+    VVM3_SUBSCRIBER_BLOCKED;
 
     public static class Type {
 
@@ -103,6 +127,15 @@
         mIsSuccess = isSuccess;
     }
 
+    OmtpEvents(int type) {
+        mType = type;
+        mIsSuccess = false;
+    }
+
+    OmtpEvents() {
+        mType = Type.OTHER;
+        mIsSuccess = false;
+    }
 
     @Type.Values
     public int getType() {
diff --git a/src/com/android/phone/vvm/omtp/VvmPhoneStateListener.java b/src/com/android/phone/vvm/omtp/VvmPhoneStateListener.java
index 64b37c6..4b81fdb 100644
--- a/src/com/android/phone/vvm/omtp/VvmPhoneStateListener.java
+++ b/src/com/android/phone/vvm/omtp/VvmPhoneStateListener.java
@@ -98,6 +98,9 @@
                 return;
             }
             helper.handleEvent(OmtpEvents.NOTIFICATION_SERVICE_LOST);
+            if (helper.isCellularDataRequired()) {
+                helper.handleEvent(OmtpEvents.DATA_NO_CONNECTION_CELLULAR_REQUIRED);
+            }
         }
         mPreviousState = state;
     }
diff --git a/src/com/android/phone/vvm/omtp/protocol/Vvm3EventHandler.java b/src/com/android/phone/vvm/omtp/protocol/Vvm3EventHandler.java
new file mode 100644
index 0000000..1b4144a
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/protocol/Vvm3EventHandler.java
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.phone.vvm.omtp.protocol;
+
+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.VoicemailChangePinDialogPreference;
+import com.android.phone.vvm.omtp.DefaultOmtpEventHandler;
+import com.android.phone.vvm.omtp.OmtpEvents;
+import com.android.phone.vvm.omtp.OmtpEvents.Type;
+import com.android.phone.vvm.omtp.utils.PhoneAccountHandleConverter;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Handles {@link OmtpEvents} when {@link Vvm3Protocol} is being used. This handler writes custom
+ * error codes into the voicemail status table so support on the dialer side is required.
+ *
+ * TODO(b/29577838) disable VVM3 by default so support on system dialer can be ensured.
+ */
+public class Vvm3EventHandler {
+
+    private static final String TAG = "Vvm3EventHandler";
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({VMS_DNS_FAILURE, VMG_DNS_FAILURE, SPG_DNS_FAILURE, VMS_NO_CELLULAR, VMG_NO_CELLULAR,
+            SPG_NO_CELLULAR, VMS_TIMEOUT, VMG_TIMEOUT, STATUS_SMS_TIMEOUT, SUBSCRIBER_BLOCKED,
+            UNKNOWN_USER, UNKNOWN_DEVICE, INVALID_PASSWORD, MAILBOX_NOT_INITIALIZED,
+            SERVICE_NOT_PROVISIONED, SERVICE_NOT_ACTIVATED, USER_BLOCKED, IMAP_GETQUOTA_ERROR,
+            IMAP_SELECT_ERROR, IMAP_ERROR, VMG_INTERNAL_ERROR, VMG_DB_ERROR,
+            VMG_COMMUNICATION_ERROR, SPG_URL_NOT_FOUND, VMG_UNKNOWN_ERROR, PIN_NOT_SET})
+    public @interface ErrorCode {
+
+    }
+
+    public static final int VMS_DNS_FAILURE = -9001;
+    public static final int VMG_DNS_FAILURE = -9002;
+    public static final int SPG_DNS_FAILURE = -9003;
+    public static final int VMS_NO_CELLULAR = -9004;
+    public static final int VMG_NO_CELLULAR = -9005;
+    public static final int SPG_NO_CELLULAR = -9006;
+    public static final int VMS_TIMEOUT = -9007;
+    public static final int VMG_TIMEOUT = -9008;
+    public static final int STATUS_SMS_TIMEOUT = -9009;
+
+    public static final int SUBSCRIBER_BLOCKED = -9990;
+    public static final int UNKNOWN_USER = -9991;
+    public static final int UNKNOWN_DEVICE = -9992;
+    public static final int INVALID_PASSWORD = -9993;
+    public static final int MAILBOX_NOT_INITIALIZED = -9994;
+    public static final int SERVICE_NOT_PROVISIONED = -9995;
+    public static final int SERVICE_NOT_ACTIVATED = -9996;
+    public static final int USER_BLOCKED = -9998;
+    public static final int IMAP_GETQUOTA_ERROR = -9997;
+    public static final int IMAP_SELECT_ERROR = -9989;
+    public static final int IMAP_ERROR = -9999;
+
+    public static final int VMG_INTERNAL_ERROR = -101;
+    public static final int VMG_DB_ERROR = -102;
+    public static final int VMG_COMMUNICATION_ERROR = -103;
+    public static final int SPG_URL_NOT_FOUND = -301;
+
+    // Non VVM3 codes:
+    public static final int VMG_UNKNOWN_ERROR = -1;
+    public static final int PIN_NOT_SET = -100;
+
+
+    public static void handleEvent(Context context, int subId, OmtpEvents event) {
+        boolean handled = false;
+        switch (event.getType()) {
+            case Type.CONFIGURATION:
+                handled = handleConfigurationEvent(context, subId, event);
+                break;
+            case Type.DATA_CHANNEL:
+                handled = handleDataChannelEvent(context, subId, event);
+                break;
+            case Type.NOTIFICATION_CHANNEL:
+                handled = handleNotificationChannelEvent(context, subId, event);
+                break;
+            case Type.OTHER:
+                handled = handleOtherEvent(context, subId, event);
+                break;
+            default:
+                com.android.services.telephony.Log
+                        .wtf(TAG, "invalid event type " + event.getType() + " for " + event);
+        }
+        if (!handled) {
+            DefaultOmtpEventHandler.handleEvent(context, subId, event);
+        }
+    }
+
+    private static boolean handleConfigurationEvent(Context context, int subId,
+            OmtpEvents event) {
+        switch (event) {
+            case CONFIG_REQUEST_STATUS_SUCCESS:
+                PhoneAccountHandle handle = PhoneAccountHandleConverter.fromSubId(subId);
+                if (VoicemailChangePinDialogPreference.getDefaultOldPin(context, handle) == null) {
+                    return false;
+                } else {
+                    postError(context, subId, PIN_NOT_SET);
+                }
+                break;
+            case CONFIG_DEFAULT_PIN_REPLACED:
+                postError(context, subId, PIN_NOT_SET);
+                break;
+            default:
+                return false;
+        }
+        return true;
+    }
+
+    private static boolean handleDataChannelEvent(Context context, int subId,
+            OmtpEvents event) {
+        switch (event) {
+            case DATA_NO_CONNECTION:
+            case DATA_NO_CONNECTION_CELLULAR_REQUIRED:
+                postError(context, subId, VMS_NO_CELLULAR);
+                break;
+            case DATA_CANNOT_RESOLVE_HOST_ON_NETWORK:
+                postError(context, subId, VMS_DNS_FAILURE);
+                break;
+            case DATA_BAD_IMAP_CREDENTIAL:
+                postError(context, subId, IMAP_ERROR);
+                break;
+            case DATA_AUTH_UNKNOWN_USER:
+                postError(context, subId, UNKNOWN_USER);
+                break;
+            case DATA_AUTH_UNKNOWN_DEVICE:
+                postError(context, subId, UNKNOWN_DEVICE);
+                break;
+            case DATA_AUTH_INVALID_PASSWORD:
+                postError(context, subId, INVALID_PASSWORD);
+                break;
+            case DATA_AUTH_MAILBOX_NOT_INITIALIZED:
+                postError(context, subId, MAILBOX_NOT_INITIALIZED);
+                break;
+            case DATA_AUTH_SERVICE_NOT_PROVISIONED:
+                postError(context, subId, SERVICE_NOT_PROVISIONED);
+                break;
+            case DATA_AUTH_SERVICE_NOT_ACTIVATED:
+                postError(context, subId, SERVICE_NOT_ACTIVATED);
+                break;
+            case DATA_AUTH_USER_IS_BLOCKED:
+                postError(context, subId, USER_BLOCKED);
+                break;
+
+            case DATA_INVALID_PORT:
+            case DATA_SSL_INVALID_HOST_NAME:
+            case DATA_CANNOT_ESTABLISH_SSL_SESSION:
+            case DATA_IOE_ON_OPEN:
+            case DATA_REJECTED_SERVER_RESPONSE:
+            case DATA_INVALID_INITIAL_SERVER_RESPONSE:
+            case DATA_SSL_EXCEPTION:
+            case DATA_ALL_SOCKET_CONNECTION_FAILED:
+                postError(context, subId, IMAP_ERROR);
+                break;
+
+            default:
+                return false;
+        }
+        return true;
+    }
+
+    private static boolean handleNotificationChannelEvent(Context context, int subId,
+            OmtpEvents event) {
+        return false;
+    }
+
+    private static boolean handleOtherEvent(Context context, int subId, OmtpEvents event) {
+        switch (event) {
+            case VVM3_NEW_USER_SETUP_FAILED:
+                postError(context, subId, MAILBOX_NOT_INITIALIZED);
+                break;
+            case VVM3_VMG_DNS_FAILURE:
+                postError(context, subId, VMG_DNS_FAILURE);
+                break;
+            case VVM3_SPG_DNS_FAILURE:
+                postError(context, subId, SPG_DNS_FAILURE);
+                break;
+            case VVM3_VMG_CONNECTION_FAILED:
+                postError(context, subId, VMG_NO_CELLULAR);
+                break;
+            case VVM3_SPG_CONNECTION_FAILED:
+                postError(context, subId, SPG_NO_CELLULAR);
+                break;
+            case VVM3_VMG_TIMEOUT:
+                postError(context, subId, VMG_TIMEOUT);
+                break;
+
+            case VVM3_SUBSCRIBER_PROVISIONED:
+                postError(context, subId, SERVICE_NOT_ACTIVATED);
+            case VVM3_SUBSCRIBER_BLOCKED:
+                postError(context, subId, SUBSCRIBER_BLOCKED);
+            default:
+                return false;
+        }
+        return true;
+    }
+
+    private static void postError(Context context, int subId, @ErrorCode int errorCode) {
+        VoicemailStatus.Editor editor = VoicemailStatus.edit(context, subId);
+
+        switch (errorCode) {
+            case VMG_DNS_FAILURE:
+            case SPG_DNS_FAILURE:
+            case VMG_NO_CELLULAR:
+            case SPG_NO_CELLULAR:
+            case VMG_TIMEOUT:
+            case SUBSCRIBER_BLOCKED:
+            case UNKNOWN_USER:
+            case UNKNOWN_DEVICE:
+            case INVALID_PASSWORD:
+            case MAILBOX_NOT_INITIALIZED:
+            case SERVICE_NOT_PROVISIONED:
+            case SERVICE_NOT_ACTIVATED:
+            case USER_BLOCKED:
+            case VMG_UNKNOWN_ERROR:
+            case SPG_URL_NOT_FOUND:
+            case VMG_INTERNAL_ERROR:
+            case VMG_DB_ERROR:
+            case VMG_COMMUNICATION_ERROR:
+            case PIN_NOT_SET:
+                editor.setConfigurationState(errorCode);
+                break;
+            case VMS_NO_CELLULAR:
+            case VMS_DNS_FAILURE:
+            case VMS_TIMEOUT:
+            case IMAP_GETQUOTA_ERROR:
+            case IMAP_SELECT_ERROR:
+            case IMAP_ERROR:
+                editor.setDataChannelState(errorCode);
+                break;
+            case STATUS_SMS_TIMEOUT:
+                editor.setNotificationChannelState(errorCode);
+                break;
+            default:
+                Log.wtf(TAG, "unknown error code: " + errorCode);
+        }
+        editor.apply();
+    }
+}
diff --git a/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java b/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java
index 6ad143f..8318318 100644
--- a/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java
+++ b/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java
@@ -17,6 +17,7 @@
 package com.android.phone.vvm.omtp.protocol;
 
 import android.annotation.Nullable;
+import android.content.Context;
 import android.net.Network;
 import android.os.Bundle;
 import android.telecom.PhoneAccountHandle;
@@ -26,6 +27,7 @@
 import com.android.phone.settings.VisualVoicemailSettingsUtil;
 import com.android.phone.settings.VoicemailChangePinDialogPreference;
 import com.android.phone.vvm.omtp.OmtpConstants;
+import com.android.phone.vvm.omtp.OmtpEvents;
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
 import com.android.phone.vvm.omtp.VvmLog;
 import com.android.phone.vvm.omtp.imap.ImapHelper;
@@ -95,6 +97,11 @@
     }
 
     @Override
+    public void handleEvent(Context context, int subId, OmtpEvents event) {
+        Vvm3EventHandler.handleEvent(context, subId, event);
+    }
+
+    @Override
     public String getCommand(String command) {
         if (command == OmtpConstants.IMAP_CHANGE_TUI_PWD_FORMAT) {
             return IMAP_CHANGE_TUI_PWD_FORMAT;
@@ -155,6 +162,7 @@
                     mConfig.requestStatus();
                 }
             } catch (MessagingException | IOException e) {
+                helper.handleEvent(OmtpEvents.VVM3_NEW_USER_SETUP_FAILED);
                 VvmLog.e(TAG, e.toString());
             }
         }
@@ -176,9 +184,7 @@
             if (helper.changePin(defaultPin, newPin) == OmtpConstants.CHANGE_PIN_SUCCESS) {
                 VoicemailChangePinDialogPreference
                         .setDefaultOldPIN(mContext, mPhoneAccount, newPin);
-
-                // TODO(b/29082418): set CONFIGURATION_STATE to VVM3_CONFIGURATION_PIN_NOT_SET
-                // to prompt the user to set the PIN
+                helper.handleEvent(OmtpEvents.CONFIG_DEFAULT_PIN_REPLACED);
             }
             VvmLog.i(TAG, "new user: PIN set");
             return true;
diff --git a/src/com/android/phone/vvm/omtp/protocol/Vvm3Subscriber.java b/src/com/android/phone/vvm/omtp/protocol/Vvm3Subscriber.java
index dc3b969..980701e 100644
--- a/src/com/android/phone/vvm/omtp/protocol/Vvm3Subscriber.java
+++ b/src/com/android/phone/vvm/omtp/protocol/Vvm3Subscriber.java
@@ -28,6 +28,7 @@
 import android.util.ArrayMap;
 
 import com.android.phone.Assert;
+import com.android.phone.vvm.omtp.OmtpEvents;
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
 import com.android.phone.vvm.omtp.VvmLog;
 import com.android.phone.vvm.omtp.sync.VvmNetworkRequestCallback;
@@ -204,6 +205,7 @@
         try {
             return future.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
         } catch (InterruptedException | ExecutionException | TimeoutException e) {
+            mHelper.handleEvent(OmtpEvents.VVM3_SPG_CONNECTION_FAILED);
             throw new ProvisioningException(e.toString());
         }
     }
@@ -219,6 +221,7 @@
         try {
             future.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
         } catch (InterruptedException | ExecutionException | TimeoutException e) {
+            mHelper.handleEvent(OmtpEvents.VVM3_SPG_CONNECTION_FAILED);
             throw new ProvisioningException(e.toString());
         }
     }
@@ -251,6 +254,7 @@
             }
             return response;
         } catch (InterruptedException | ExecutionException | TimeoutException e) {
+            mHelper.handleEvent(OmtpEvents.VVM3_VMG_CONNECTION_FAILED);
             throw new ProvisioningException(e.toString());
         }
     }