Support XCHANGE_TUI_PWD

The XCHANGE_TUI_PWD OMTP IMAP command allows the user to change the PIN
required to dial in their voicemail inbox.

A prefernce in voicemail settings has been added to access this feature.
https://screenshot.googleplex.com/X9rWDsKB0Bw

The preference will launch a dialog to enter the old and new PIN
the EditText has numberPassword format
https://screenshot.googleplex.com/L3WW6q5igVw

A modal progress dialog will spin when the PIN change is processing.
https://screenshot.googleplex.com/J9Gfv8zq1oo

If any error occurred, a dialog will be shown with the error message
https://screenshot.googleplex.com/LBq7G3FXG8M

Change-Id: I8a083b2a22bd85bbdfdae16593f4827b44049776
Fixes:29056644
diff --git a/res/layout/voicemail_dialog_change_pin.xml b/res/layout/voicemail_dialog_change_pin.xml
new file mode 100644
index 0000000..a64768b
--- /dev/null
+++ b/res/layout/voicemail_dialog_change_pin.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+  android:layout_width="match_parent"
+  android:layout_height="match_parent"
+  android:orientation="vertical"
+  android:padding="?android:attr/dialogPreferredPadding">
+
+    <TextView
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:text="@string/vm_change_pin_old_pin"
+      android:labelFor="@+id/vm_old_pin"/>
+
+    <EditText android:id="@id/vm_old_pin"
+
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:textColor="?android:attr/textColorSecondary"
+      android:inputType="numberPassword"/>
+
+    <TextView
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:text="@string/vm_change_pin_new_pin"
+      android:labelFor="@+id/vm_new_pin"/>
+
+    <EditText android:id="@id/vm_new_pin"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:textColor="?android:attr/textColorSecondary"
+      android:inputType="numberPassword"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index b30f8ce..0fbd08d 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -330,6 +330,26 @@
     <!-- Call settings screen, "Voicemail" screen, default option - Your Carrier -->
     <string name="voicemail_default">Your carrier</string>
 
+    <!-- Hint for the old PIN field in the change vociemail PIN dialog -->
+    <string name="vm_change_pin_old_pin">Old PIN</string>
+    <!-- Hint for the new PIN field in the change vociemail PIN dialog -->
+    <string name="vm_change_pin_new_pin">New PIN</string>
+
+    <!-- Message on the dialog when PIN changing is in progress -->
+    <string name="vm_change_pin_progress_message">Changing PIN</string>
+    <!-- Error message for the voicemail PIN change if the PIN is too short -->
+    <string name="vm_change_pin_error_too_short">The new PIN is too short.</string>
+    <!-- Error message for the voicemail PIN change if the PIN is too long -->
+    <string name="vm_change_pin_error_too_long">The new PIN is too long.</string>
+    <!-- Error message for the voicemail PIN change if the PIN is too weak -->
+    <string name="vm_change_pin_error_too_weak">The new PIN is too weak. A strong password should not have continuous sequence or repeated digits.</string>
+    <!-- Error message for the voicemail PIN change if the old PIN entered doesn't match  -->
+    <string name="vm_change_pin_error_mismatch">The old PIN does not match.</string>
+    <!-- Error message for the voicemail PIN change if the new PIN contains invalid character -->
+    <string name="vm_change_pin_error_invalid">The new PIN contains invalid characters.</string>
+    <!-- Error message for the voicemail PIN change if operation has failed -->
+    <string name="vm_change_pin_error_system_error">Unable to change PIN</string>
+
     <!-- networks setting strings --><skip/>
     <!-- Mobile network settings screen title -->
     <string name="mobile_networks">Cellular network settings</string>
@@ -1235,6 +1255,9 @@
     <!-- Visual voicemail on/off title [CHAR LIMIT=40] -->
     <string name="voicemail_visual_voicemail_switch_title">Visual Voicemail</string>
 
+    <!-- Voicemail change PIN dialog title [CHAR LIMIT=40] -->
+    <string name="voicemail_change_pin_dialog_title">Change PIN</string>
+
     <!-- Voicemail ringtone title. The user clicks on this preference to select
          which sound to play when a voicemail notification is received.
          [CHAR LIMIT=30] -->
@@ -1302,6 +1325,8 @@
     <string name="voicemail_notification_vibrate_key">voicemail_notification_vibrate_key</string>
     <!-- DO NOT TRANSLATE. Internal key for a visual voicemail preference. -->
     <string name="voicemail_visual_voicemail_key">voicemail_visual_voicemail_key</string>
+    <!-- DO NOT TRANSLATE. Internal key for a voicemail change pin preference. -->
+    <string name="voicemail_change_pin_key">voicemail_change_pin_key</string>
     <!-- DO NOT TRANSLATE. Internal key for tty mode preference. -->
     <string name="tty_mode_key">button_tty_mode_key</string>
     <!-- DO NOT TRANSLATE. Internal key for a voicemail notification preference. -->
diff --git a/res/xml/voicemail_settings.xml b/res/xml/voicemail_settings.xml
index 9334566..734d9d7 100644
--- a/res/xml/voicemail_settings.xml
+++ b/res/xml/voicemail_settings.xml
@@ -60,8 +60,13 @@
         android:key="@string/voicemail_notification_vibrate_key"
         android:title="@string/voicemail_notification_vibrate_when_title"
         android:persistent="true" />
+
     <SwitchPreference
         android:key="@string/voicemail_visual_voicemail_key"
         android:title="@string/voicemail_visual_voicemail_switch_title" />"
 
+    <com.android.phone.settings.VoicemailChangePinDialogPreference
+        android:key="@string/voicemail_change_pin_key"
+        android:title="@string/voicemail_change_pin_dialog_title" />
+
 </PreferenceScreen>
diff --git a/src/com/android/phone/common/mail/store/ImapConnection.java b/src/com/android/phone/common/mail/store/ImapConnection.java
index ad47acc..c1e0e84 100644
--- a/src/com/android/phone/common/mail/store/ImapConnection.java
+++ b/src/com/android/phone/common/mail/store/ImapConnection.java
@@ -289,17 +289,17 @@
     }
 
 
-    void destroyResponses() {
+    public void destroyResponses() {
         if (mParser != null) {
             mParser.destroyResponses();
         }
     }
 
-    ImapResponse readResponse() throws IOException, MessagingException {
+    public ImapResponse readResponse() throws IOException, MessagingException {
         return mParser.readResponse();
     }
 
-    List<ImapResponse> executeSimpleCommand(String command)
+    public List<ImapResponse> executeSimpleCommand(String command)
             throws IOException, MessagingException{
         return executeSimpleCommand(command, false);
     }
@@ -324,7 +324,8 @@
         return getCommandResponses();
     }
 
-    String sendCommand(String command, boolean sensitive) throws IOException, MessagingException {
+    public String sendCommand(String command, boolean sensitive)
+            throws IOException, MessagingException {
         open();
 
         if (mTransport == null) {
diff --git a/src/com/android/phone/common/mail/store/ImapFolder.java b/src/com/android/phone/common/mail/store/ImapFolder.java
index 13c7424..81f156f 100644
--- a/src/com/android/phone/common/mail/store/ImapFolder.java
+++ b/src/com/android/phone/common/mail/store/ImapFolder.java
@@ -17,14 +17,9 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
-import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
-import android.provider.VoicemailContract;
 import android.provider.VoicemailContract.Status;
-import android.telecom.Voicemail;
 import android.text.TextUtils;
 import android.util.Base64DataException;
-import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.phone.common.R;
diff --git a/src/com/android/phone/settings/VoicemailChangePinDialogPreference.java b/src/com/android/phone/settings/VoicemailChangePinDialogPreference.java
new file mode 100644
index 0000000..122a29e
--- /dev/null
+++ b/src/com/android/phone/settings/VoicemailChangePinDialogPreference.java
@@ -0,0 +1,165 @@
+/*
+ * 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.settings;
+
+import android.app.AlertDialog;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.net.Network;
+import android.preference.DialogPreference;
+import android.telecom.PhoneAccountHandle;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+import android.widget.EditText;
+
+import com.android.phone.R;
+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.imap.ImapHelper;
+import com.android.phone.vvm.omtp.sync.VvmNetworkRequestCallback;
+
+/**
+ * Dialog to change the voicemail PIN. The TUI PIN is used when accessing traditional voicemail through
+ * phone call.
+ */
+public class VoicemailChangePinDialogPreference extends DialogPreference {
+
+    private static final String TAG = "VmChangePinDialog";
+
+    private EditText mOldPin;
+    private EditText mNewPin;
+    private PhoneAccountHandle mPhoneAccountHandle;
+
+    private ProgressDialog mProgressDialog;
+
+    public VoicemailChangePinDialogPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public VoicemailChangePinDialogPreference(Context context, AttributeSet attrs,
+            int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    @Override
+    protected View onCreateDialogView() {
+        setDialogLayoutResource(R.layout.voicemail_dialog_change_pin);
+
+        View dialog = super.onCreateDialogView();
+
+        mOldPin = (EditText) dialog.findViewById(R.id.vm_old_pin);
+        mNewPin = (EditText) dialog.findViewById(R.id.vm_new_pin);
+
+        return dialog;
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        if (positiveResult) {
+            processPinChange();
+        }
+        super.onDialogClosed(positiveResult);
+    }
+
+    public VoicemailChangePinDialogPreference setPhoneAccountHandle(PhoneAccountHandle handle) {
+        mPhoneAccountHandle = handle;
+        return this;
+    }
+
+    private void processPinChange() {
+        mProgressDialog = new ProgressDialog(getContext());
+        mProgressDialog.setCancelable(false);
+        mProgressDialog.setMessage(getContext().getString(R.string.vm_change_pin_progress_message));
+        mProgressDialog.show();
+
+        ChangePinNetworkRequestCallback callback = new ChangePinNetworkRequestCallback();
+        callback.requestNetwork();
+    }
+
+    private void finishPinChange() {
+        mProgressDialog.dismiss();
+    }
+
+    private void showError(@ChangePinResult int result) {
+        if (result != OmtpConstants.CHANGE_PIN_SUCCESS) {
+            CharSequence message;
+            switch (result) {
+                case OmtpConstants.CHANGE_PIN_TOO_SHORT:
+                    message = getContext().getString(R.string.vm_change_pin_error_too_short);
+                    break;
+                case OmtpConstants.CHANGE_PIN_TOO_LONG:
+                    message = getContext().getString(R.string.vm_change_pin_error_too_long);
+                    break;
+
+                case OmtpConstants.CHANGE_PIN_TOO_WEAK:
+                    message = getContext().getString(R.string.vm_change_pin_error_too_weak);
+                    break;
+                case OmtpConstants.CHANGE_PIN_INVALID_CHARACTER:
+                    message = getContext().getString(R.string.vm_change_pin_error_invalid);
+                    break;
+                case OmtpConstants.CHANGE_PIN_MISMATCH:
+                    message = getContext().getString(R.string.vm_change_pin_error_mismatch);
+                    break;
+                case OmtpConstants.CHANGE_PIN_SYSTEM_ERROR:
+                    message = getContext().getString(R.string.vm_change_pin_error_system_error);
+                    break;
+                default:
+                    Log.wtf(TAG, "Unexpected ChangePinResult " + result);
+                    return;
+            }
+            new AlertDialog.Builder(getContext())
+                    .setMessage(message)
+                    .setPositiveButton(android.R.string.ok, null)
+                    .show();
+        }
+    }
+
+    private class ChangePinNetworkRequestCallback extends VvmNetworkRequestCallback {
+
+        public ChangePinNetworkRequestCallback() {
+            super(getContext(), mPhoneAccountHandle);
+        }
+
+        @Override
+        public void onAvailable(Network network) {
+            super.onAvailable(network);
+            ImapHelper helper = new ImapHelper(getContext(), mPhoneAccountHandle, network);
+            try {
+                @ChangePinResult int result =
+                        helper.changePin(mOldPin.getText().toString(),
+                                mNewPin.getText().toString());
+                finishPinChange();
+                if (result != OmtpConstants.CHANGE_PIN_SUCCESS) {
+                    showError(result);
+                }
+            } catch (MessagingException e) {
+                finishPinChange();
+                showError(OmtpConstants.CHANGE_PIN_SYSTEM_ERROR);
+            }
+
+        }
+
+        @Override
+        public void onFailed(String reason) {
+            super.onFailed(reason);
+            finishPinChange();
+            showError(OmtpConstants.CHANGE_PIN_SYSTEM_ERROR);
+        }
+    }
+}
diff --git a/src/com/android/phone/settings/VoicemailSettingsActivity.java b/src/com/android/phone/settings/VoicemailSettingsActivity.java
index d403161..b425e0e 100644
--- a/src/com/android/phone/settings/VoicemailSettingsActivity.java
+++ b/src/com/android/phone/settings/VoicemailSettingsActivity.java
@@ -42,6 +42,7 @@
 import com.android.internal.telephony.PhoneConstants;
 import com.android.phone.EditPhoneNumberPreference;
 import com.android.phone.PhoneGlobals;
+import com.android.phone.PhoneUtils;
 import com.android.phone.R;
 import com.android.phone.SubscriptionInfoHelper;
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
@@ -200,7 +201,7 @@
     private VoicemailRingtonePreference mVoicemailNotificationRingtone;
     private CheckBoxPreference mVoicemailNotificationVibrate;
     private SwitchPreference mVoicemailVisualVoicemail;
-
+    private VoicemailChangePinDialogPreference mVoicemailChangePinPreference;
 
     //*********************************************************************************************
     // Preference Activity Methods
@@ -259,12 +260,19 @@
 
         mVoicemailVisualVoicemail = (SwitchPreference) findPreference(
                 getResources().getString(R.string.voicemail_visual_voicemail_key));
+
+        mVoicemailChangePinPreference = (VoicemailChangePinDialogPreference) findPreference(
+                getResources().getString(R.string.voicemail_change_pin_key));
         if (mOmtpVvmCarrierConfigHelper.isValid()) {
             mVoicemailVisualVoicemail.setOnPreferenceChangeListener(this);
             mVoicemailVisualVoicemail.setChecked(
                     VisualVoicemailSettingsUtil.isVisualVoicemailEnabled(mPhone));
+
+            mVoicemailChangePinPreference
+                    .setPhoneAccountHandle(PhoneUtils.makePstnPhoneAccountHandle(mPhone));
         } else {
             prefSet.removePreference(mVoicemailVisualVoicemail);
+            prefSet.removePreference(mVoicemailChangePinPreference);
         }
 
         updateVMPreferenceWidgets(mVoicemailProviders.getValue());
@@ -388,12 +396,15 @@
         } else if (preference.getKey().equals(mVoicemailVisualVoicemail.getKey())) {
             boolean isEnabled = (Boolean) objValue;
             VisualVoicemailSettingsUtil.setVisualVoicemailEnabled(mPhone, isEnabled, true);
+            PreferenceScreen prefSet = getPreferenceScreen();
             if (isEnabled) {
                 OmtpVvmSourceManager.getInstance(mPhone.getContext()).addPhoneStateListener(mPhone);
                 mOmtpVvmCarrierConfigHelper.startActivation();
+                prefSet.addPreference(mVoicemailChangePinPreference);
             } else {
                 OmtpVvmSourceManager.getInstance(mPhone.getContext()).removeSource(mPhone);
                 mOmtpVvmCarrierConfigHelper.startDeactivation();
+                prefSet.removePreference(mVoicemailChangePinPreference);
             }
         }
 
diff --git a/src/com/android/phone/vvm/omtp/OmtpConstants.java b/src/com/android/phone/vvm/omtp/OmtpConstants.java
index 896ffe0..d4de9aa 100644
--- a/src/com/android/phone/vvm/omtp/OmtpConstants.java
+++ b/src/com/android/phone/vvm/omtp/OmtpConstants.java
@@ -15,13 +15,16 @@
  */
 package com.android.phone.vvm.omtp;
 
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.HashMap;
 import java.util.Map;
 
 /**
- * Wrapper class to hold relevant OMTP constants as defined in the OMTP spec.
- * <p>
- * In essence this is a programmatic representation of the relevant portions of OMTP spec.
+ * Wrapper class to hold relevant OMTP constants as defined in the OMTP spec. <p> In essence this is
+ * a programmatic representation of the relevant portions of OMTP spec.
  */
 public class OmtpConstants {
     public static final String SMS_FIELD_SEPARATOR = ";";
@@ -185,6 +188,43 @@
         put(RETURN_CODE, RETURN_CODE_VALUES);
     }};
 
+    /**
+     * IMAP command extensions
+     */
+
+    /**
+     * OMTP spec v1.3 2.3.1 Change password request syntax
+     */
+    public static final String IMAP_CHANGE_TUI_PWD_FORMAT = "XCHANGE_TUI_PWD PWD=%1s OLD_PWD=%2s";
+
+    /**
+     * Possible NO responses for CHANGE_TUI_PWD
+     */
+
+    public static final String RESPONSE_CHANGE_PIN_TOO_SHORT = "password too short";
+    public static final String RESPONSE_CHANGE_PIN_TOO_LONG = "password too long";
+    public static final String RESPONSE_CHANGE_PIN_TOO_WEAK = "password too weak";
+    public static final String RESPONSE_CHANGE_PIN_MISMATCH = "old password mismatch";
+    public static final String RESPONSE_CHANGE_PIN_INVALID_CHARACTER =
+            "password contains invalid characters";
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(value = {CHANGE_PIN_SUCCESS, CHANGE_PIN_TOO_SHORT, CHANGE_PIN_TOO_LONG,
+            CHANGE_PIN_TOO_WEAK, CHANGE_PIN_MISMATCH, CHANGE_PIN_INVALID_CHARACTER,
+            CHANGE_PIN_SYSTEM_ERROR})
+
+    public @interface ChangePinResult {
+
+    }
+
+    public static final int CHANGE_PIN_SUCCESS = 0;
+    public static final int CHANGE_PIN_TOO_SHORT = 1;
+    public static final int CHANGE_PIN_TOO_LONG = 2;
+    public static final int CHANGE_PIN_TOO_WEAK = 3;
+    public static final int CHANGE_PIN_MISMATCH = 4;
+    public static final int CHANGE_PIN_INVALID_CHARACTER = 5;
+    public static final int CHANGE_PIN_SYSTEM_ERROR = 6;
+
     /** Indicates the client is Google visual voicemail version 1.0. */
     public static final String CLIENT_TYPE_GOOGLE_10 = "google.vvm.10";
 }
diff --git a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
index 537b3a7..3097550 100644
--- a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
+++ b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
@@ -138,6 +138,11 @@
     }
 
     @Nullable
+    public VisualVoicemailProtocol getProtocol() {
+        return mProtocol;
+    }
+
+    @Nullable
     public Set<String> getCarrierVvmPackageNames() {
         Set<String> names = getCarrierVvmPackageNames(mCarrierConfig);
         if (names != null) {
diff --git a/src/com/android/phone/vvm/omtp/imap/ImapHelper.java b/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
index 9305d7b..cc7cb14 100644
--- a/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
+++ b/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
@@ -40,12 +40,15 @@
 import com.android.phone.common.mail.Multipart;
 import com.android.phone.common.mail.TempDirectory;
 import com.android.phone.common.mail.internet.MimeMessage;
+import com.android.phone.common.mail.store.ImapConnection;
 import com.android.phone.common.mail.store.ImapFolder;
 import com.android.phone.common.mail.store.ImapStore;
 import com.android.phone.common.mail.store.imap.ImapConstants;
+import com.android.phone.common.mail.store.imap.ImapResponse;
 import com.android.phone.common.mail.utils.LogUtils;
 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.OmtpVvmCarrierConfigHelper;
 import com.android.phone.vvm.omtp.fetch.VoicemailFetchedCallback;
 import com.android.phone.vvm.omtp.sync.OmtpVvmSyncService.TranscriptionFetchedCallback;
@@ -58,12 +61,14 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Locale;
 
 /**
  * A helper interface to abstract commands sent across IMAP interface for a given account.
  */
 public class ImapHelper {
-    private final String TAG = "ImapHelper";
+
+    private static final String TAG = "ImapHelper";
 
     private ImapFolder mFolder;
     private ImapStore mImapStore;
@@ -375,6 +380,55 @@
         }
     }
 
+
+    @ChangePinResult
+    public int changePin(String oldPin, String newPin)
+            throws MessagingException {
+        ImapConnection connection = mImapStore.getConnection();
+        try {
+            String command = getConfig().getProtocol()
+                    .getCommand(OmtpConstants.IMAP_CHANGE_TUI_PWD_FORMAT);
+            connection.sendCommand(
+                    String.format(Locale.US, command, newPin, oldPin), true);
+            return getChangePinResultFromImapResponse(connection.readResponse());
+        } catch (IOException ioe) {
+            return OmtpConstants.CHANGE_PIN_SYSTEM_ERROR;
+        } finally {
+            connection.destroyResponses();
+        }
+    }
+
+    @ChangePinResult
+    private static int getChangePinResultFromImapResponse(ImapResponse response)
+            throws MessagingException {
+        if (!response.isTagged()) {
+            throw new MessagingException(MessagingException.SERVER_ERROR,
+                    "tagged response expected");
+        }
+        if (!response.isOk()) {
+            String message = response.getStringOrEmpty(1).getString();
+            LogUtils.d(TAG, "change PIN failed: " + message);
+            if (OmtpConstants.RESPONSE_CHANGE_PIN_TOO_SHORT.equals(message)) {
+                return OmtpConstants.CHANGE_PIN_TOO_SHORT;
+            }
+            if (OmtpConstants.RESPONSE_CHANGE_PIN_TOO_LONG.equals(message)) {
+                return OmtpConstants.CHANGE_PIN_TOO_LONG;
+            }
+            if (OmtpConstants.RESPONSE_CHANGE_PIN_TOO_WEAK.equals(message)) {
+                return OmtpConstants.CHANGE_PIN_TOO_WEAK;
+            }
+            if (OmtpConstants.RESPONSE_CHANGE_PIN_MISMATCH.equals(message)) {
+                return OmtpConstants.CHANGE_PIN_MISMATCH;
+            }
+            if (OmtpConstants.RESPONSE_CHANGE_PIN_INVALID_CHARACTER.equals(message)) {
+                return OmtpConstants.CHANGE_PIN_INVALID_CHARACTER;
+            }
+            return OmtpConstants.CHANGE_PIN_SYSTEM_ERROR;
+        }
+        LogUtils.d(TAG, "change PIN succeeded");
+        return OmtpConstants.CHANGE_PIN_SUCCESS;
+    }
+
     public void updateQuota() {
         try {
             mFolder = openImapFolder(ImapFolder.MODE_READ_WRITE);
diff --git a/src/com/android/phone/vvm/omtp/protocol/CvvmProtocol.java b/src/com/android/phone/vvm/omtp/protocol/CvvmProtocol.java
index 9c4d531..9185583 100644
--- a/src/com/android/phone/vvm/omtp/protocol/CvvmProtocol.java
+++ b/src/com/android/phone/vvm/omtp/protocol/CvvmProtocol.java
@@ -18,17 +18,30 @@
 
 import android.telephony.SmsManager;
 
+import com.android.phone.vvm.omtp.OmtpConstants;
 import com.android.phone.vvm.omtp.sms.OmtpCvvmMessageSender;
 import com.android.phone.vvm.omtp.sms.OmtpMessageSender;
 
 /**
  * A flavor of OMTP protocol with a different mobile originated (MO) format
+ *
+ * Used by carriers such as T-Mobile
  */
 public class CvvmProtocol extends VisualVoicemailProtocol {
 
+    private static String IMAP_CHANGE_TUI_PWD_FORMAT = "CHANGE_TUI_PWD PWD=%1s OLD_PWD=%2s";
+
     @Override
     public OmtpMessageSender createMessageSender(SmsManager smsManager, short applicationPort,
             String destinationNumber) {
         return new OmtpCvvmMessageSender(smsManager, applicationPort, destinationNumber);
     }
+
+    @Override
+    public String getCommand(String command) {
+        if (command == OmtpConstants.IMAP_CHANGE_TUI_PWD_FORMAT) {
+            return IMAP_CHANGE_TUI_PWD_FORMAT;
+        }
+        return super.getCommand(command);
+    }
 }
diff --git a/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java b/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java
index bc7dc82..eab83f5 100644
--- a/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java
+++ b/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java
@@ -51,4 +51,16 @@
 
     public abstract OmtpMessageSender createMessageSender(SmsManager smsManager,
             short applicationPort, String destinationNumber);
+
+    /**
+     * Translate an OMTP IMAP command to the protocol specific one. For example, changing the TUI
+     * password on OMTP is XCHANGE_TUI_PWD, but on CVVM and VVM3 it is CHANGE_TUI_PWD.
+     *
+     * @param command A String command in {@link com.android.phone.vvm.omtp.OmtpConstants}, the exact
+     * instance should be used instead of its' value.
+     * @returns Translated command, or {@code null} if not available in this protocol
+     */
+    public String getCommand(String command) {
+        return command;
+    }
 }
diff --git a/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java b/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java
index f53d270..e7f271a 100644
--- a/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java
+++ b/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java
@@ -20,17 +20,22 @@
 import android.telephony.SmsManager;
 import android.util.Log;
 
+import com.android.phone.vvm.omtp.OmtpConstants;
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
 import com.android.phone.vvm.omtp.sms.OmtpMessageSender;
 import com.android.phone.vvm.omtp.sms.Vvm3MessageSender;
 
 /**
  * A flavor of OMTP protocol with a different provisioning process
+ *
+ * Used by carriers such as Verizon Wireless
  */
 public class Vvm3Protocol extends VisualVoicemailProtocol {
 
     private static String TAG = "Vvm3Protocol";
 
+    private static String IMAP_CHANGE_TUI_PWD_FORMAT = "CHANGE_TUI_PWD PWD=%1s OLD_PWD=%2s";
+
     public Vvm3Protocol() {
         Log.d(TAG, "Vvm3Protocol created");
     }
@@ -60,4 +65,12 @@
             String destinationNumber) {
         return new Vvm3MessageSender(smsManager, applicationPort, destinationNumber);
     }
+
+    @Override
+    public String getCommand(String command) {
+        if (command == OmtpConstants.IMAP_CHANGE_TUI_PWD_FORMAT) {
+            return IMAP_CHANGE_TUI_PWD_FORMAT;
+        }
+        return super.getCommand(command);
+    }
 }