Centralize VVM error Processing

To prepare for vendor specific error codes every error that is written to
the voicemail status table is centralized to the VoicemailProtocol,
which determines what error code will be actually written.

+ Use PhoneAccountHandleConverter to convert subId instead of PhoneUtils.
  PhoneAccountHandleConverter still uses PhoneUtils under the hood but
  it will be changed to using public API in the future.

Bug:28968317
Bug:28977379
Change-Id: I69232b276ec0d456fb9ca1fb08f25b2d51d89462
diff --git a/src/com/android/phone/VoicemailStatus.java b/src/com/android/phone/VoicemailStatus.java
index ca01de8..062e734 100644
--- a/src/com/android/phone/VoicemailStatus.java
+++ b/src/com/android/phone/VoicemailStatus.java
@@ -23,7 +23,8 @@
 import android.provider.VoicemailContract;
 import android.provider.VoicemailContract.Status;
 import android.telecom.PhoneAccountHandle;
-import android.telephony.SubscriptionManager;
+
+import com.android.phone.vvm.omtp.utils.PhoneAccountHandleConverter;
 
 public class VoicemailStatus {
 
@@ -86,8 +87,6 @@
     }
 
     public static Editor edit(Context context, int subId) {
-        PhoneAccountHandle phone = PhoneUtils.makePstnPhoneAccountHandle(
-                SubscriptionManager.getPhoneId(subId));
-        return new Editor(context, phone);
+        return new Editor(context, PhoneAccountHandleConverter.fromSubId(subId));
     }
 }
diff --git a/src/com/android/phone/common/mail/MailTransport.java b/src/com/android/phone/common/mail/MailTransport.java
index cc09044..47b273c 100644
--- a/src/com/android/phone/common/mail/MailTransport.java
+++ b/src/com/android/phone/common/mail/MailTransport.java
@@ -17,11 +17,11 @@
 
 import android.content.Context;
 import android.net.Network;
-import android.provider.VoicemailContract.Status;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.phone.common.mail.store.ImapStore;
 import com.android.phone.common.mail.utils.LogUtils;
+import com.android.phone.vvm.omtp.OmtpEvents;
 import com.android.phone.vvm.omtp.imap.ImapHelper;
 
 import java.io.BufferedInputStream;
@@ -117,7 +117,7 @@
                 }
             } catch (IOException ioe) {
                 LogUtils.d(TAG, ioe.toString());
-                mImapHelper.setDataChannelState(Status.DATA_CHANNEL_STATE_SERVER_CONNECTION_ERROR);
+                mImapHelper.handleEvent(OmtpEvents.DATA_CANNOT_RESOLVE_HOST_ON_NETWORK);
                 throw new MessagingException(MessagingException.IOERROR, ioe.toString());
             }
         }
@@ -147,8 +147,7 @@
                 LogUtils.d(TAG, ioe.toString());
                 if (socketAddresses.size() == 0) {
                     // Only throw an error when there are no more sockets to try.
-                    mImapHelper
-                            .setDataChannelState(Status.DATA_CHANNEL_STATE_SERVER_CONNECTION_ERROR);
+                    mImapHelper.handleEvent(OmtpEvents.DATA_ALL_SOCKET_CONNECTION_FAILED);
                     throw new MessagingException(MessagingException.IOERROR, ioe.toString());
                 }
             } finally {
@@ -247,7 +246,7 @@
 
         SSLSession session = ssl.getSession();
         if (session == null) {
-            mImapHelper.setDataChannelState(Status.DATA_CHANNEL_STATE_COMMUNICATION_ERROR);
+            mImapHelper.handleEvent(OmtpEvents.DATA_CANNOT_ESTABLISH_SSL_SESSION);
             throw new SSLException("Cannot verify SSL socket without session");
         }
         // TODO: Instead of reporting the name of the server we think we're connecting to,
@@ -255,7 +254,7 @@
         // in the verifier code and is not available in the verifier API, and extracting the
         // CN & alts is beyond the scope of this patch.
         if (!HOSTNAME_VERIFIER.verify(hostname, session)) {
-            mImapHelper.setDataChannelState(Status.DATA_CHANNEL_STATE_COMMUNICATION_ERROR);
+            mImapHelper.handleEvent(OmtpEvents.DATA_SSL_INVALID_HOST_NAME);
             throw new SSLPeerUnverifiedException("Certificate hostname not useable for server: "
                     + session.getPeerPrincipal());
         }
diff --git a/src/com/android/phone/common/mail/store/ImapConnection.java b/src/com/android/phone/common/mail/store/ImapConnection.java
index c1e0e84..48a516c 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.provider.VoicemailContract.Status;
 import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Base64;
@@ -31,6 +30,7 @@
 import com.android.phone.common.mail.store.imap.ImapResponseParser;
 import com.android.phone.common.mail.store.imap.ImapUtility;
 import com.android.phone.common.mail.utils.LogUtils;
+import com.android.phone.vvm.omtp.OmtpEvents;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -114,7 +114,7 @@
             ImapResponse response = mParser.readResponse();
             if (!response.isOk()) {
                 mImapStore.getImapHelper()
-                        .setDataChannelState(Status.DATA_CHANNEL_STATE_SERVER_ERROR);
+                        .handleEvent(OmtpEvents.DATA_INVALID_INITIAL_SERVER_RESPONSE);
                 throw new MessagingException(
                         MessagingException.AUTHENTICATION_FAILED_OR_SERVER_ERROR,
                         "Invalid server initial response");
@@ -128,12 +128,11 @@
             doLogin();
         } catch (SSLException e) {
             LogUtils.d(TAG, "SSLException ", e);
-            mImapStore.getImapHelper().setDataChannelState(Status.DATA_CHANNEL_STATE_SERVER_ERROR);
+            mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_SSL_EXCEPTION);
             throw new CertificateValidationException(e.getMessage(), e);
         } catch (IOException ioe) {
             LogUtils.d(TAG, "IOException", ioe);
-            mImapStore.getImapHelper()
-                    .setDataChannelState(Status.DATA_CHANNEL_STATE_COMMUNICATION_ERROR);
+            mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_IOE_ON_OPEN);
             throw ioe;
         } finally {
             destroyResponses();
@@ -189,8 +188,7 @@
             if (ImapConstants.AUTHENTICATIONFAILED.equals(code) ||
                     ImapConstants.EXPIRED.equals(code) ||
                     (ImapConstants.NO.equals(status) && TextUtils.isEmpty(code))) {
-                mImapStore.getImapHelper()
-                        .setDataChannelState(Status.DATA_CHANNEL_STATE_BAD_CONFIGURATION);
+                mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_BAD_IMAP_CREDENTIAL);
                 throw new AuthenticationFailedException(alertText, ie);
             }
 
@@ -365,7 +363,7 @@
             final String alert = response.getAlertTextOrEmpty().getString();
             final String responseCode = response.getResponseCodeOrEmpty().getString();
             destroyResponses();
-            mImapStore.getImapHelper().setDataChannelState(Status.DATA_CHANNEL_STATE_SERVER_ERROR);
+            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)) {
 
diff --git a/src/com/android/phone/common/mail/store/ImapFolder.java b/src/com/android/phone/common/mail/store/ImapFolder.java
index 81f156f..fe3df30 100644
--- a/src/com/android/phone/common/mail/store/ImapFolder.java
+++ b/src/com/android/phone/common/mail/store/ImapFolder.java
@@ -17,7 +17,6 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
-import android.provider.VoicemailContract.Status;
 import android.text.TextUtils;
 import android.util.Base64DataException;
 
@@ -44,6 +43,7 @@
 import com.android.phone.common.mail.store.imap.ImapString;
 import com.android.phone.common.mail.utils.LogUtils;
 import com.android.phone.common.mail.utils.Utility;
+import com.android.phone.vvm.omtp.OmtpEvents;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -102,6 +102,7 @@
                         return;
 
                     } catch (IOException ioe) {
+                        mStore.getImapHelper().handleEvent(OmtpEvents.DATA_GENERIC_IMAP_IOE);
                         ioExceptionHandler(mConnection, ioe);
                     } finally {
                         destroyResponses();
@@ -202,6 +203,7 @@
                 return Utility.EMPTY_STRINGS; // Not found
             } catch (IOException ioe) {
                 LogUtils.d(TAG, "IOException in search: " + searchCriteria, ioe);
+                mStore.getImapHelper().handleEvent(OmtpEvents.DATA_GENERIC_IMAP_IOE);
                 throw ioExceptionHandler(mConnection, ioe);
             }
         } finally {
@@ -427,6 +429,7 @@
                 }
             } while (!response.isTagged());
         } catch (IOException ioe) {
+            mStore.getImapHelper().handleEvent(OmtpEvents.DATA_GENERIC_IMAP_IOE);
             throw ioExceptionHandler(mConnection, ioe);
         }
     }
@@ -662,6 +665,7 @@
         try {
             handleUntaggedResponses(mConnection.executeSimpleCommand(ImapConstants.EXPUNGE));
         } catch (IOException ioe) {
+            mStore.getImapHelper().handleEvent(OmtpEvents.DATA_GENERIC_IMAP_IOE);
             throw ioExceptionHandler(mConnection, ioe);
         } finally {
             destroyResponses();
@@ -698,6 +702,7 @@
                     allFlags));
 
         } catch (IOException ioe) {
+            mStore.getImapHelper().handleEvent(OmtpEvents.DATA_GENERIC_IMAP_IOE);
             throw ioExceptionHandler(mConnection, ioe);
         } finally {
             destroyResponses();
@@ -726,7 +731,7 @@
                     mMode = MODE_READ_WRITE;
                 }
             } else if (response.isTagged()) { // Not OK
-                mStore.getImapHelper().setDataChannelState(Status.DATA_CHANNEL_STATE_SERVER_ERROR);
+                mStore.getImapHelper().handleEvent(OmtpEvents.DATA_MAILBOX_OPEN_FAILED);
                 throw new MessagingException("Can't open mailbox: "
                         + response.getStatusResponseTextOrEmpty());
             }
@@ -769,6 +774,7 @@
                 }
             }
         } catch (IOException ioe) {
+            mStore.getImapHelper().handleEvent(OmtpEvents.DATA_GENERIC_IMAP_IOE);
             throw ioExceptionHandler(mConnection, ioe);
         } finally {
             destroyResponses();
@@ -789,7 +795,6 @@
             mConnection = null; // To prevent close() from returning the connection to the pool.
             close(false);
         }
-        mStore.getImapHelper().setDataChannelState(Status.DATA_CHANNEL_STATE_COMMUNICATION_ERROR);
         return new MessagingException(MessagingException.IOERROR, "IO Error", ioe);
     }
 
diff --git a/src/com/android/phone/vvm/omtp/DefaultOmtpEventHandler.java b/src/com/android/phone/vvm/omtp/DefaultOmtpEventHandler.java
new file mode 100644
index 0000000..6816d4c
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/DefaultOmtpEventHandler.java
@@ -0,0 +1,160 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.provider.VoicemailContract;
+import android.provider.VoicemailContract.Status;
+
+import com.android.phone.VoicemailStatus;
+import com.android.phone.vvm.omtp.OmtpEvents.Type;
+import com.android.services.telephony.Log;
+
+public class DefaultOmtpEventHandler {
+
+    private static final String TAG = "DefErrorCodeHandler";
+
+    public static void handleEvent(Context context, int subId, OmtpEvents event) {
+        switch (event.getType()) {
+            case Type.CONFIGURATION:
+                handleConfigurationEvent(context, subId, event);
+                break;
+            case Type.DATA_CHANNEL:
+                handleDataChannelEvent(context, subId, event);
+                break;
+            case Type.NOTIFICATION_CHANNEL:
+                handleNotificationChannelEvent(context, subId, event);
+                break;
+            case Type.OTHER:
+                handleOtherEvent(context, subId, event);
+                break;
+            default:
+                Log.wtf(TAG, "invalid event type " + event.getType() + " for " + event);
+        }
+    }
+
+    private static void handleConfigurationEvent(Context context, int subId,
+            OmtpEvents event) {
+        switch (event) {
+            case CONFIG_REQUEST_STATUS_SUCCESS:
+                VoicemailStatus.edit(context, subId)
+                        .setConfigurationState(VoicemailContract.Status.CONFIGURATION_STATE_OK)
+                        .setNotificationChannelState(Status.NOTIFICATION_CHANNEL_STATE_OK)
+                        .apply();
+                break;
+            default:
+                Log.wtf(TAG, "invalid configuration event " + event);
+        }
+    }
+
+    private static void handleDataChannelEvent(Context context, int subId,
+            OmtpEvents event) {
+        switch (event) {
+            case DATA_IMAP_OPERATION_COMPLETED:
+                VoicemailStatus.edit(context, subId)
+                        .setDataChannelState(Status.DATA_CHANNEL_STATE_OK)
+                        .apply();
+                break;
+
+            case DATA_NO_CONNECTION:
+                VoicemailStatus.edit(context, subId)
+                        .setDataChannelState(Status.DATA_CHANNEL_STATE_NO_CONNECTION)
+                        .apply();
+                break;
+
+            case DATA_NO_CONNECTION_CELLULAR_REQUIRED:
+                VoicemailStatus.edit(context, subId)
+                        .setDataChannelState(
+                                Status.DATA_CHANNEL_STATE_NO_CONNECTION_CELLULAR_REQUIRED)
+                        .apply();
+                break;
+            case DATA_INVALID_PORT:
+                VoicemailStatus.edit(context, subId)
+                        .setDataChannelState(
+                                VoicemailContract.Status.DATA_CHANNEL_STATE_BAD_CONFIGURATION)
+                        .apply();
+                break;
+            case DATA_CANNOT_RESOLVE_HOST_ON_NETWORK:
+                VoicemailStatus.edit(context, subId)
+                        .setDataChannelState(
+                                VoicemailContract.Status.DATA_CHANNEL_STATE_SERVER_CONNECTION_ERROR)
+                        .apply();
+                break;
+            case DATA_SSL_INVALID_HOST_NAME:
+            case DATA_CANNOT_ESTABLISH_SSL_SESSION:
+            case DATA_IOE_ON_OPEN:
+                VoicemailStatus.edit(context, subId)
+                        .setDataChannelState(
+                                VoicemailContract.Status.DATA_CHANNEL_STATE_COMMUNICATION_ERROR)
+                        .apply();
+                break;
+            case DATA_BAD_IMAP_CREDENTIAL:
+                VoicemailStatus.edit(context, subId)
+                        .setDataChannelState(
+                                VoicemailContract.Status.DATA_CHANNEL_STATE_BAD_CONFIGURATION)
+                        .apply();
+                break;
+
+            case DATA_REJECTED_SERVER_RESPONSE:
+            case DATA_INVALID_INITIAL_SERVER_RESPONSE:
+            case DATA_SSL_EXCEPTION:
+            case DATA_ALL_SOCKET_CONNECTION_FAILED:
+                VoicemailStatus.edit(context, subId)
+                        .setDataChannelState(
+                                VoicemailContract.Status.DATA_CHANNEL_STATE_SERVER_ERROR)
+                        .apply();
+                break;
+
+            default:
+                Log.wtf(TAG, "invalid data channel event " + event);
+        }
+    }
+
+    private static void handleNotificationChannelEvent(Context context, int subId,
+            OmtpEvents event) {
+        switch (event) {
+            case NOTIFICATION_IN_SERVICE:
+                VoicemailStatus.edit(context, subId)
+                        .setNotificationChannelState(Status.NOTIFICATION_CHANNEL_STATE_OK)
+                        .apply();
+                break;
+            case NOTIFICATION_SERVICE_LOST:
+                VoicemailStatus.edit(context, subId)
+                        .setNotificationChannelState(
+                                Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION)
+                        .apply();
+                break;
+            default:
+                Log.wtf(TAG, "invalid notification channel event " + event);
+        }
+    }
+
+    private static void handleOtherEvent(Context context, int subId, OmtpEvents event) {
+        switch (event) {
+            case OTHER_SOURCE_REMOVED:
+                VoicemailStatus.edit(context, subId)
+                        .setConfigurationState(Status.CONFIGURATION_STATE_NOT_CONFIGURED)
+                        .setNotificationChannelState(
+                                Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION)
+                        .setDataChannelState(Status.DATA_CHANNEL_STATE_NO_CONNECTION)
+                        .apply();
+                break;
+            default:
+                Log.wtf(TAG, "invalid other event " + event);
+        }
+    }
+}
diff --git a/src/com/android/phone/vvm/omtp/OmtpEvents.java b/src/com/android/phone/vvm/omtp/OmtpEvents.java
new file mode 100644
index 0000000..6de692e
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/OmtpEvents.java
@@ -0,0 +1,116 @@
+/*
+ * 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;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Events internal to the OMTP client. These should be translated into {@link
+ * android.provider.VoicemailContract.Status} error codes before writing into the voicemail status
+ * table.
+ */
+public enum OmtpEvents {
+
+    // Configuration State
+    CONFIG_REQUEST_STATUS_SUCCESS(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),
+    // No connection to the internet, and the carrier requires cellular data
+    DATA_NO_CONNECTION_CELLULAR_REQUIRED(Type.DATA_CHANNEL, false),
+    // No connection to the internet.
+    DATA_NO_CONNECTION(Type.DATA_CHANNEL, false),
+    // Address lookup for the server hostname failed. DNS error?
+    DATA_CANNOT_RESOLVE_HOST_ON_NETWORK(Type.DATA_CHANNEL, false),
+    // All destination address that resolves to the server hostname are rejected or timed out
+    DATA_ALL_SOCKET_CONNECTION_FAILED(Type.DATA_CHANNEL, false),
+    // 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),
+    // Identity of the server cannot be verified.
+    DATA_SSL_INVALID_HOST_NAME(Type.DATA_CHANNEL, false),
+    // The server rejected our username/password
+    DATA_BAD_IMAP_CREDENTIAL(Type.DATA_CHANNEL, false),
+    // A command to the server didn't result with an "OK" or continuation request
+    DATA_REJECTED_SERVER_RESPONSE(Type.DATA_CHANNEL, false),
+    // The server did not greet us with a "OK", possibly not a IMAP server.
+    DATA_INVALID_INITIAL_SERVER_RESPONSE(Type.DATA_CHANNEL, false),
+    // An IOException occurred while trying to open an ImapConnection
+    // TODO: reduce scope
+    DATA_IOE_ON_OPEN(Type.DATA_CHANNEL, false),
+    // The SELECT command on a mailbox is rejected
+    DATA_MAILBOX_OPEN_FAILED(Type.DATA_CHANNEL, false),
+    // An IOException has occurred
+    // TODO: reduce scope
+    DATA_GENERIC_IMAP_IOE(Type.DATA_CHANNEL, false),
+    // An SslException has occurred while opening an ImapConnection
+    // TODO: reduce scope
+    DATA_SSL_EXCEPTION(Type.DATA_CHANNEL, false),
+
+    // Notification Channel
+
+    // Cell signal restored, can received VVM SMSs
+    NOTIFICATION_IN_SERVICE(Type.NOTIFICATION_CHANNEL, true),
+    // Cell signal lost, cannot received VVM SMSs
+    NOTIFICATION_SERVICE_LOST(Type.NOTIFICATION_CHANNEL, false),
+
+
+    // Other
+    OTHER_SOURCE_REMOVED(Type.OTHER, false);
+
+
+    public static class Type {
+
+        @Retention(RetentionPolicy.SOURCE)
+        @IntDef({CONFIGURATION, DATA_CHANNEL, NOTIFICATION_CHANNEL, OTHER})
+        public @interface Values {
+
+        }
+
+        public static final int CONFIGURATION = 1;
+        public static final int DATA_CHANNEL = 2;
+        public static final int NOTIFICATION_CHANNEL = 3;
+        public static final int OTHER = 4;
+    }
+
+    private final int mType;
+    private final boolean mIsSuccess;
+
+    OmtpEvents(int type, boolean isSuccess) {
+        mType = type;
+        mIsSuccess = isSuccess;
+    }
+
+
+    @Type.Values
+    public int getType() {
+        return mType;
+    }
+
+    public boolean isSuccess() {
+        return mIsSuccess;
+    }
+
+}
diff --git a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
index 3097550..eb888f1 100644
--- a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
+++ b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
@@ -37,17 +37,15 @@
 import java.util.Set;
 
 /**
- * Manages carrier dependent visual voicemail configuration values.
- * The primary source is the value retrieved from CarrierConfigManager. If CarrierConfigManager does
- * not provide the config (KEY_VVM_TYPE_STRING is empty, or "hidden" configs), then the value
- * hardcoded in telephony will be used (in res/xml/vvm_config.xml)
+ * Manages carrier dependent visual voicemail configuration values. The primary source is the value
+ * retrieved from CarrierConfigManager. If CarrierConfigManager does not provide the config
+ * (KEY_VVM_TYPE_STRING is empty, or "hidden" configs), then the value hardcoded in telephony will
+ * be used (in res/xml/vvm_config.xml)
  *
  * Hidden configs are new configs that are planned for future APIs, or miscellaneous settings that
  * may clutter CarrierConfigManager too much.
  *
- * The current hidden configs are:
- * {@link #getSslPort()}
- * {@link #getDisabledCapabilities()}
+ * The current hidden configs are: {@link #getSslPort()} {@link #getDisabledCapabilities()}
  */
 public class OmtpVvmCarrierConfigHelper {
 
@@ -294,6 +292,12 @@
         }
     }
 
+    public void handleEvent(OmtpEvents event) {
+        if (mProtocol != null) {
+            mProtocol.handleEvent(mContext, mSubId, event);
+        }
+    }
+
     @Nullable
     private PersistableBundle getCarrierConfig() {
         if (!SubscriptionManager.isValidSubscriptionId(mSubId)) {
diff --git a/src/com/android/phone/vvm/omtp/SimChangeReceiver.java b/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
index 830243a..01d6bf9 100644
--- a/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
+++ b/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
@@ -28,10 +28,10 @@
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyIntents;
-import com.android.phone.PhoneUtils;
 import com.android.phone.R;
 import com.android.phone.settings.VisualVoicemailSettingsUtil;
 import com.android.phone.vvm.omtp.sync.OmtpVvmSourceManager;
+import com.android.phone.vvm.omtp.utils.PhoneAccountHandleConverter;
 
 /**
  * This class listens to the {@link CarrierConfigManager#ACTION_CARRIER_CONFIG_CHANGED} and {@link
@@ -88,8 +88,7 @@
         OmtpVvmCarrierConfigHelper carrierConfigHelper =
                 new OmtpVvmCarrierConfigHelper(context, subId);
         if (carrierConfigHelper.isValid()) {
-            PhoneAccountHandle phoneAccount = PhoneUtils.makePstnPhoneAccountHandle(
-                    SubscriptionManager.getPhoneId(subId));
+            PhoneAccountHandle phoneAccount = PhoneAccountHandleConverter.fromSubId(subId);
 
             boolean isUserSet = VisualVoicemailSettingsUtil.isVisualVoicemailUserSet(
                     context, phoneAccount);
diff --git a/src/com/android/phone/vvm/omtp/VvmPhoneStateListener.java b/src/com/android/phone/vvm/omtp/VvmPhoneStateListener.java
index 02cf443..3438de2 100644
--- a/src/com/android/phone/vvm/omtp/VvmPhoneStateListener.java
+++ b/src/com/android/phone/vvm/omtp/VvmPhoneStateListener.java
@@ -17,7 +17,6 @@
 
 import android.content.Context;
 import android.content.Intent;
-import android.provider.VoicemailContract;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
@@ -25,15 +24,16 @@
 
 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.VoicemailStatusQueryHelper;
+import com.android.phone.vvm.omtp.utils.PhoneAccountHandleConverter;
 
 /**
  * Check if service is lost and indicate this in the voicemail status.
  */
 public class VvmPhoneStateListener extends PhoneStateListener {
+
     private static final String TAG = "VvmPhoneStateListener";
 
     private PhoneAccountHandle mPhoneAccount;
@@ -57,18 +57,17 @@
             return;
         }
 
+        int subId = PhoneAccountHandleConverter.toSubId(mPhoneAccount);
+        OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(mContext, subId);
+
         if (state == ServiceState.STATE_IN_SERVICE) {
             VoicemailStatusQueryHelper voicemailStatusQueryHelper =
                     new VoicemailStatusQueryHelper(mContext);
             if (voicemailStatusQueryHelper.isVoicemailSourceConfigured(mPhoneAccount)) {
                 if (!voicemailStatusQueryHelper.isNotificationsChannelActive(mPhoneAccount)) {
                     Log.v(TAG, "Notifications channel is active for " + mPhoneAccount.getId());
-                    VoicemailStatus.edit(mContext, mPhoneAccount)
-                            .setNotificationChannelState(
-                                    VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_OK)
-                            .apply();
-                    PhoneGlobals.getInstance().clearMwiIndicator(
-                            PhoneUtils.getSubIdForPhoneAccountHandle(mPhoneAccount));
+                    helper.handleEvent(OmtpEvents.NOTIFICATION_IN_SERVICE);
+                    PhoneGlobals.getInstance().clearMwiIndicator(subId);
                 }
             }
 
@@ -89,9 +88,7 @@
                 // Otherwise initiate an activation because this means that an OMTP source was
                 // recognized but either the activation text was not successfully sent or a response
                 // was not received.
-                OmtpVvmCarrierConfigHelper carrierConfigHelper = new OmtpVvmCarrierConfigHelper(
-                        mContext, PhoneUtils.getSubIdForPhoneAccountHandle(mPhoneAccount));
-                carrierConfigHelper.startActivation();
+                helper.startActivation();
             }
         } else {
             Log.v(TAG, "Notifications channel is inactive for " + mPhoneAccount.getId());
@@ -102,11 +99,7 @@
             if (!OmtpVvmSourceManager.getInstance(mContext).isVvmSourceRegistered(mPhoneAccount)) {
                 return;
             }
-
-            VoicemailStatus.edit(mContext, mPhoneAccount)
-                    .setNotificationChannelState(
-                            VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION)
-                    .apply();
+            helper.handleEvent(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 da60ad1..0095f53 100644
--- a/src/com/android/phone/vvm/omtp/fetch/FetchVoicemailReceiver.java
+++ b/src/com/android/phone/vvm/omtp/fetch/FetchVoicemailReceiver.java
@@ -16,6 +16,7 @@
 package com.android.phone.vvm.omtp.fetch;
 
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -47,10 +48,12 @@
     final static String[] PROJECTION = new String[] {
             Voicemails.SOURCE_DATA,      // 0
             Voicemails.PHONE_ACCOUNT_ID, // 1
+            Voicemails.PHONE_ACCOUNT_COMPONENT_NAME, // 2
     };
 
     public static final int SOURCE_DATA = 0;
     public static final int PHONE_ACCOUNT_ID = 1;
+    public static final int PHONE_ACCOUNT_COMPONENT_NAME = 2;
 
     // Timeout used to call ConnectivityManager.requestNetwork
     private static final int NETWORK_REQUEST_TIMEOUT_MILLIS = 60 * 1000;
@@ -105,7 +108,10 @@
                         }
                     }
 
-                    mPhoneAccount = PhoneUtils.makePstnPhoneAccountHandle(accountId);
+                    mPhoneAccount = new PhoneAccountHandle(
+                            ComponentName.unflattenFromString(
+                                    cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME)),
+                            cursor.getString(PHONE_ACCOUNT_ID));
                     if (!OmtpVvmSourceManager.getInstance(context)
                             .isVvmSourceRegistered(mPhoneAccount)) {
                         Log.w(TAG, "Account not registered - cannot retrieve message.");
diff --git a/src/com/android/phone/vvm/omtp/imap/ImapHelper.java b/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
index cc7cb14..415ebc5 100644
--- a/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
+++ b/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
@@ -22,7 +22,6 @@
 import android.net.NetworkInfo;
 import android.preference.PreferenceManager;
 import android.provider.VoicemailContract;
-import android.provider.VoicemailContract.Status;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.Voicemail;
 import android.util.Base64;
@@ -49,6 +48,7 @@
 import com.android.phone.settings.VisualVoicemailSettingsUtil;
 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.OmtpVvmCarrierConfigHelper;
 import com.android.phone.vvm.omtp.fetch.VoicemailFetchedCallback;
 import com.android.phone.vvm.omtp.sync.OmtpVvmSyncService.TranscriptionFetchedCallback;
@@ -115,9 +115,7 @@
             mImapStore = new ImapStore(
                     context, this, username, password, port, serverName, auth, network);
         } catch (NumberFormatException e) {
-            VoicemailStatus.edit(mContext, mPhoneAccount)
-                    .setDataChannelState(Status.DATA_CHANNEL_STATE_BAD_CONFIGURATION)
-                    .apply();
+            mConfig.handleEvent(OmtpEvents.DATA_INVALID_PORT);
             LogUtils.w(TAG, "Could not parse port number");
         }
 
@@ -161,10 +159,8 @@
         return setFlags(voicemails, Flag.DELETED);
     }
 
-    public void setDataChannelState(int dataChannelState) {
-        VoicemailStatus.edit(mContext, mPhoneAccount)
-                .setDataChannelState(dataChannelState)
-                .apply();
+    public void handleEvent(OmtpEvents event) {
+        mConfig.handleEvent(event);
     }
 
     /**
diff --git a/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java b/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java
index eab83f5..df8b735 100644
--- a/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java
+++ b/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java
@@ -16,9 +16,12 @@
 
 package com.android.phone.vvm.omtp.protocol;
 
+import android.content.Context;
 import android.os.Bundle;
 import android.telephony.SmsManager;
 
+import com.android.phone.vvm.omtp.DefaultOmtpEventHandler;
+import com.android.phone.vvm.omtp.OmtpEvents;
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
 import com.android.phone.vvm.omtp.sms.OmtpMessageSender;
 
@@ -63,4 +66,8 @@
     public String getCommand(String command) {
         return command;
     }
+
+    public void handleEvent(Context context, int subId, OmtpEvents event) {
+        DefaultOmtpEventHandler.handleEvent(context, subId, event);
+    }
 }
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
index bb3f758..ac932e4 100644
--- a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
@@ -23,22 +23,21 @@
 import android.os.Bundle;
 import android.os.UserManager;
 import android.provider.VoicemailContract;
-import android.provider.VoicemailContract.Status;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.Voicemail;
-import android.telephony.SubscriptionManager;
 import android.util.Log;
 
 import com.android.phone.PhoneGlobals;
 import com.android.phone.PhoneUtils;
-import com.android.phone.VoicemailStatus;
 import com.android.phone.settings.VisualVoicemailSettingsUtil;
 import com.android.phone.vvm.omtp.LocalLogHelper;
 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.sync.OmtpVvmSourceManager;
 import com.android.phone.vvm.omtp.sync.OmtpVvmSyncService;
 import com.android.phone.vvm.omtp.sync.VoicemailsQueryHelper;
+import com.android.phone.vvm.omtp.utils.PhoneAccountHandleConverter;
 
 /**
  * Receive SMS messages and send for processing by the OMTP visual voicemail source.
@@ -57,9 +56,8 @@
         }
 
         mContext = context;
-        PhoneAccountHandle phone = PhoneUtils.makePstnPhoneAccountHandle(
-                SubscriptionManager.getPhoneId(
-                        intent.getExtras().getInt(VoicemailContract.EXTRA_VOICEMAIL_SMS_SUBID)));
+        int subId = intent.getExtras().getInt(VoicemailContract.EXTRA_VOICEMAIL_SMS_SUBID);
+        PhoneAccountHandle phone = PhoneAccountHandleConverter.fromSubId(subId);
 
         if (phone == null) {
             Log.i(TAG, "Received message for null phone account");
@@ -88,7 +86,7 @@
             LocalLogHelper.log(TAG, "Received Status sms for " + phone.getId());
             StatusMessage message = new StatusMessage(data);
             if (message.getProvisioningStatus().equals(OmtpConstants.SUBSCRIBER_READY)) {
-                updateSource(phone, message);
+                updateSource(phone, subId, message);
             } else {
                 Log.v(TAG, "Subscriber not ready, start provisioning");
                 startProvisioning(phone, data);
@@ -151,15 +149,13 @@
         }
     }
 
-    private void updateSource(PhoneAccountHandle phone, StatusMessage message) {
+    private void updateSource(PhoneAccountHandle phone, int subId, StatusMessage message) {
         OmtpVvmSourceManager vvmSourceManager =
                 OmtpVvmSourceManager.getInstance(mContext);
 
         if (OmtpConstants.SUCCESS.equals(message.getReturnCode())) {
-            VoicemailStatus.edit(mContext, phone)
-                    .setConfigurationState(VoicemailContract.Status.CONFIGURATION_STATE_OK)
-                    .setNotificationChannelState(Status.NOTIFICATION_CHANNEL_STATE_OK)
-                    .apply();
+            OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(mContext, subId);
+            helper.handleEvent(OmtpEvents.CONFIG_REQUEST_STATUS_SUCCESS);
 
             // Save the IMAP credentials in preferences so they are persistent and can be retrieved.
             VisualVoicemailSettingsUtil.setVisualVoicemailCredentialsFromStatusMessage(
@@ -175,8 +171,7 @@
                     true /* firstAttempt */);
             mContext.startService(serviceIntent);
 
-            PhoneGlobals.getInstance().clearMwiIndicator(
-                    PhoneUtils.getSubIdForPhoneAccountHandle(phone));
+            PhoneGlobals.getInstance().clearMwiIndicator(subId);
         } else {
             Log.w(TAG, "Visual voicemail not available for subscriber.");
             // Override default isEnabled setting to false since visual voicemail is unable to
diff --git a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSourceManager.java b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSourceManager.java
index 811b05a..1b9e53f 100644
--- a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSourceManager.java
+++ b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSourceManager.java
@@ -16,7 +16,6 @@
 package com.android.phone.vvm.omtp.sync;
 
 import android.content.Context;
-import android.provider.VoicemailContract.Status;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.PhoneStateListener;
 import android.telephony.SubscriptionManager;
@@ -24,8 +23,10 @@
 
 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;
@@ -106,11 +107,8 @@
     }
 
     public void removeSource(PhoneAccountHandle phoneAccount) {
-        VoicemailStatus.edit(mContext, phoneAccount)
-                .setConfigurationState(Status.CONFIGURATION_STATE_NOT_CONFIGURED)
-                .setDataChannelState(Status.DATA_CHANNEL_STATE_NO_CONNECTION)
-                .setNotificationChannelState(Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION)
-                .apply();
+        new OmtpVvmCarrierConfigHelper(mContext, PhoneAccountHandleConverter.toSubId(phoneAccount))
+                .handleEvent(OmtpEvents.OTHER_SOURCE_REMOVED);
         removePhoneStateListener(phoneAccount);
         mActiveVvmSources.remove(phoneAccount);
         OmtpVvmSyncService.cancelAllRetries(mContext, phoneAccount);
diff --git a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
index d140936..c0411ec 100644
--- a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
+++ b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
@@ -24,7 +24,6 @@
 import android.net.NetworkInfo;
 import android.net.Uri;
 import android.provider.VoicemailContract;
-import android.provider.VoicemailContract.Status;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.Voicemail;
 import android.text.TextUtils;
@@ -34,6 +33,7 @@
 import com.android.phone.VoicemailStatus;
 import com.android.phone.settings.VisualVoicemailSettingsUtil;
 import com.android.phone.vvm.omtp.LocalLogHelper;
+import com.android.phone.vvm.omtp.OmtpEvents;
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
 import com.android.phone.vvm.omtp.fetch.VoicemailFetchedCallback;
 import com.android.phone.vvm.omtp.imap.ImapHelper;
@@ -263,9 +263,7 @@
                     VisualVoicemailSettingsUtil.resetVisualVoicemailRetryInterval(this,
                             phoneAccount);
 
-                    VoicemailStatus.edit(this, phoneAccount)
-                            .setDataChannelState(Status.DATA_CHANNEL_STATE_OK)
-                            .apply();
+                    imapHelper.handleEvent(OmtpEvents.DATA_IMAP_OPERATION_COMPLETED);
                     return;
                 }
             }
diff --git a/src/com/android/phone/vvm/omtp/sync/VvmNetworkRequestCallback.java b/src/com/android/phone/vvm/omtp/sync/VvmNetworkRequestCallback.java
index 1c1c243..b28f9d6 100644
--- a/src/com/android/phone/vvm/omtp/sync/VvmNetworkRequestCallback.java
+++ b/src/com/android/phone/vvm/omtp/sync/VvmNetworkRequestCallback.java
@@ -23,12 +23,11 @@
 import android.net.NetworkRequest;
 import android.os.Handler;
 import android.os.Looper;
-import android.provider.VoicemailContract.Status;
 import android.telecom.PhoneAccountHandle;
 import android.util.Log;
 
 import com.android.phone.PhoneUtils;
-import com.android.phone.VoicemailStatus;
+import com.android.phone.vvm.omtp.OmtpEvents;
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
 
 /**
@@ -146,13 +145,9 @@
     public void onFailed(String reason) {
         Log.d(TAG, "onFailed: " + reason);
         if (mCarrierConfigHelper.isCellularDataRequired()) {
-            VoicemailStatus.edit(mContext, mPhoneAccount)
-                    .setDataChannelState(Status.DATA_CHANNEL_STATE_NO_CONNECTION_CELLULAR_REQUIRED)
-                    .apply();
+            mCarrierConfigHelper.handleEvent(OmtpEvents.DATA_NO_CONNECTION_CELLULAR_REQUIRED);
         } else {
-            VoicemailStatus.edit(mContext, mPhoneAccount)
-                    .setDataChannelState(Status.DATA_CHANNEL_STATE_NO_CONNECTION)
-                    .apply();
+            mCarrierConfigHelper.handleEvent(OmtpEvents.DATA_NO_CONNECTION);
         }
         releaseNetwork();
     }
diff --git a/src/com/android/phone/vvm/omtp/utils/PhoneAccountHandleConverter.java b/src/com/android/phone/vvm/omtp/utils/PhoneAccountHandleConverter.java
new file mode 100644
index 0000000..0474452
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/utils/PhoneAccountHandleConverter.java
@@ -0,0 +1,43 @@
+/*
+ * 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.utils;
+
+import android.telecom.PhoneAccountHandle;
+import android.telephony.SubscriptionManager;
+
+import com.android.phone.PhoneUtils;
+
+/**
+ * Utility to convert between PhoneAccountHandle and subId, which is a common operation in OMTP
+ * client
+ *
+ * TODO(b/28977379): remove dependency on PhoneUtils and use public APIs
+ */
+public class PhoneAccountHandleConverter {
+
+    public static PhoneAccountHandle fromSubId(int subId) {
+        return PhoneUtils.makePstnPhoneAccountHandle(
+                SubscriptionManager.getPhoneId(subId));
+    }
+
+    public static int toSubId(PhoneAccountHandle handle) {
+        return PhoneUtils.getSubIdForPhoneAccountHandle(handle);
+    }
+
+    private PhoneAccountHandleConverter() {
+    }
+}