Merge "VT Settings Toggle Should be Blocked During Active Calls" into nyc-mr1-dev
diff --git a/res/xml/vvm_config.xml b/res/xml/vvm_config.xml
index d55fdb2..3bfdf75 100644
--- a/res/xml/vvm_config.xml
+++ b/res/xml/vvm_config.xml
@@ -135,5 +135,8 @@
     <string name="vvm_client_prefix_string">//VZWVVM</string>
     <boolean name="vvm_cellular_data_required_bool" value="true"/>
     <boolean name="vvm_legacy_mode_enabled_bool" value="true"/>
+    <!-- VVM3 specific value for the voicemail management gateway to use if the SMS didn't provide
+         one -->
+    <string name="default_vmg_url">https://mobile.vzw.com/VMGIMS/VMServices</string>
   </pbundle_as_map>
 </list>
\ No newline at end of file
diff --git a/src/com/android/phone/settings/VoicemailChangePinActivity.java b/src/com/android/phone/settings/VoicemailChangePinActivity.java
index 68cc621..fcf5bd3 100644
--- a/src/com/android/phone/settings/VoicemailChangePinActivity.java
+++ b/src/com/android/phone/settings/VoicemailChangePinActivity.java
@@ -44,7 +44,6 @@
 import android.widget.TextView;
 import android.widget.TextView.OnEditorActionListener;
 import android.widget.Toast;
-
 import com.android.phone.PhoneUtils;
 import com.android.phone.R;
 import com.android.phone.common.mail.MessagingException;
@@ -55,6 +54,7 @@
 import com.android.phone.vvm.omtp.VisualVoicemailPreferences;
 import com.android.phone.vvm.omtp.VvmLog;
 import com.android.phone.vvm.omtp.imap.ImapHelper;
+import com.android.phone.vvm.omtp.imap.ImapHelper.InitializingException;
 import com.android.phone.vvm.omtp.sync.VvmNetworkRequestCallback;
 
 /**
@@ -594,7 +594,7 @@
                 @ChangePinResult int result =
                         helper.changePin(mOldPin, mNewPin);
                 sendResult(result);
-            } catch (MessagingException e) {
+            } catch (InitializingException | MessagingException e) {
                 sendResult(OmtpConstants.CHANGE_PIN_SYSTEM_ERROR);
             }
         }
diff --git a/src/com/android/phone/vvm/omtp/ActivationTask.java b/src/com/android/phone/vvm/omtp/ActivationTask.java
index b37f0d3..101a96f 100644
--- a/src/com/android/phone/vvm/omtp/ActivationTask.java
+++ b/src/com/android/phone/vvm/omtp/ActivationTask.java
@@ -22,12 +22,10 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.telecom.PhoneAccountHandle;
-
 import com.android.phone.Assert;
 import com.android.phone.PhoneGlobals;
 import com.android.phone.vvm.omtp.protocol.VisualVoicemailProtocol;
 import com.android.phone.vvm.omtp.scheduling.BaseTask;
-import com.android.phone.vvm.omtp.scheduling.MinimalIntervalPolicy;
 import com.android.phone.vvm.omtp.scheduling.RetryPolicy;
 import com.android.phone.vvm.omtp.sms.StatusMessage;
 import com.android.phone.vvm.omtp.sms.StatusSmsFetcher;
@@ -35,7 +33,6 @@
 import com.android.phone.vvm.omtp.sync.OmtpVvmSyncService;
 import com.android.phone.vvm.omtp.sync.SyncTask;
 import com.android.phone.vvm.omtp.utils.PhoneAccountHandleConverter;
-
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -53,7 +50,6 @@
 
     private static final int RETRY_TIMES = 4;
     private static final int RETRY_INTERVAL_MILLIS = 5_000;
-    private static final int MINIMAL_INTERVAL_MILLIS = 30_000;
 
     private static final String EXTRA_MESSAGE_DATA_BUNDLE = "extra_message_data_bundle";
 
@@ -62,7 +58,6 @@
     public ActivationTask() {
         super(TASK_ACTIVATION);
         addPolicy(new RetryPolicy(RETRY_TIMES, RETRY_INTERVAL_MILLIS));
-        addPolicy(new MinimalIntervalPolicy(MINIMAL_INTERVAL_MILLIS));
     }
 
     public static void start(Context context, int subId, @Nullable Bundle data) {
diff --git a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
index b50e291..00a70be 100644
--- a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
+++ b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
@@ -27,14 +27,12 @@
 import android.telephony.VisualVoicemailSmsFilterSettings;
 import android.text.TextUtils;
 import android.util.ArraySet;
-
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.phone.VoicemailStatus;
 import com.android.phone.vvm.omtp.protocol.VisualVoicemailProtocol;
 import com.android.phone.vvm.omtp.protocol.VisualVoicemailProtocolFactory;
 import com.android.phone.vvm.omtp.sms.StatusMessage;
 import com.android.phone.vvm.omtp.utils.PhoneAccountHandleConverter;
-
 import java.util.Arrays;
 import java.util.Set;
 
@@ -162,6 +160,14 @@
         return mProtocol;
     }
 
+    /**
+     * @returns arbitrary String stored in the config file. Used for protocol specific values.
+     */
+    @Nullable
+    public String getString(String key) {
+        return (String) getValue(key);
+    }
+
     @Nullable
     public Set<String> getCarrierVvmPackageNames() {
         Set<String> names = getCarrierVvmPackageNames(mCarrierConfig);
diff --git a/src/com/android/phone/vvm/omtp/SimChangeReceiver.java b/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
index 375109d..24f7a2a 100644
--- a/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
+++ b/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
@@ -24,6 +24,7 @@
 import android.os.UserManager;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.CarrierConfigManager;
+import android.telephony.ServiceState;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 
@@ -89,14 +90,22 @@
             PhoneAccountHandle phoneAccount = PhoneAccountHandleConverter.fromSubId(subId);
 
             if (VisualVoicemailSettingsUtil.isEnabled(context, phoneAccount)) {
-                VvmLog.i(TAG, "Sim state or carrier config changed: requesting"
-                        + " activation for " + subId);
-
+                VvmLog.i(TAG, "Sim state or carrier config changed for " + subId);
                 // Add a phone state listener so that changes to the communication channels
                 // can be recorded.
                 OmtpVvmSourceManager.getInstance(context).addPhoneStateListener(
                         phoneAccount);
-                carrierConfigHelper.startActivation();
+                // Check for signal before activating. The event often happen while boot and the
+                // network is not connected yet. Launching activation will likely to cause the SMS
+                // sending to fail and waste unnecessary time waiting for time out.
+                if (context.getSystemService(TelephonyManager.class)
+                        .getServiceStateForSubscriber(subId).getState()
+                        == ServiceState.STATE_IN_SERVICE) {
+                    VvmLog.i(TAG, "Sim/config changed while in service, requesting activation");
+                    carrierConfigHelper.startActivation();
+                } else {
+                    VvmLog.i(TAG, "Sim/config changed while not in service.");
+                }
             } else {
                 if (carrierConfigHelper.isLegacyModeEnabled()) {
                     // SMS still need to be filtered under legacy mode.
diff --git a/src/com/android/phone/vvm/omtp/fetch/FetchVoicemailReceiver.java b/src/com/android/phone/vvm/omtp/fetch/FetchVoicemailReceiver.java
index d267e68..5ec190f 100644
--- a/src/com/android/phone/vvm/omtp/fetch/FetchVoicemailReceiver.java
+++ b/src/com/android/phone/vvm/omtp/fetch/FetchVoicemailReceiver.java
@@ -35,6 +35,7 @@
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
 import com.android.phone.vvm.omtp.VvmLog;
 import com.android.phone.vvm.omtp.imap.ImapHelper;
+import com.android.phone.vvm.omtp.imap.ImapHelper.InitializingException;
 import com.android.phone.vvm.omtp.sync.OmtpVvmSourceManager;
 import com.android.phone.vvm.omtp.sync.VvmNetworkRequestCallback;
 
@@ -161,11 +162,6 @@
                         VvmLog.i(TAG, "fetching voicemail, retry count=" + mRetryCount);
                         try (ImapHelper imapHelper = new ImapHelper(mContext, mPhoneAccount,
                                 network)) {
-                            if (!imapHelper.isSuccessfullyInitialized()) {
-                                VvmLog.w(TAG, "Can't retrieve Imap credentials.");
-                                return;
-                            }
-
                             boolean success = imapHelper.fetchVoicemailPayload(
                                     new VoicemailFetchedCallback(mContext, mUri), mUid);
                             if (!success && mRetryCount > 0) {
@@ -174,6 +170,9 @@
                             } else {
                                 return;
                             }
+                        } catch (InitializingException e) {
+                          VvmLog.w(TAG, "Can't retrieve Imap credentials ", e);
+                            return;
                         }
                     }
                 } finally {
diff --git a/src/com/android/phone/vvm/omtp/imap/ImapHelper.java b/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
index 5f7722c..532991f 100644
--- a/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
+++ b/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
@@ -23,7 +23,6 @@
 import android.telecom.PhoneAccountHandle;
 import android.telecom.Voicemail;
 import android.util.Base64;
-
 import com.android.phone.PhoneUtils;
 import com.android.phone.VoicemailStatus;
 import com.android.phone.common.mail.Address;
@@ -50,9 +49,6 @@
 import com.android.phone.vvm.omtp.VvmLog;
 import com.android.phone.vvm.omtp.fetch.VoicemailFetchedCallback;
 import com.android.phone.vvm.omtp.sync.OmtpVvmSyncService.TranscriptionFetchedCallback;
-
-import libcore.io.IoUtils;
-
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.Closeable;
@@ -61,6 +57,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
+import libcore.io.IoUtils;
 
 /**
  * A helper interface to abstract commands sent across IMAP interface for a given account.
@@ -85,13 +82,21 @@
 
     private final OmtpVvmCarrierConfigHelper mConfig;
 
-    public ImapHelper(Context context, PhoneAccountHandle phoneAccount, Network network) {
+    public class InitializingException extends Exception {
+
+        public InitializingException(String message) {
+            super(message);
+        }
+    }
+
+    public ImapHelper(Context context, PhoneAccountHandle phoneAccount, Network network)
+        throws InitializingException {
         this(context, new OmtpVvmCarrierConfigHelper(context,
                 PhoneUtils.getSubIdForPhoneAccountHandle(phoneAccount)), phoneAccount, network);
     }
 
     public ImapHelper(Context context, OmtpVvmCarrierConfigHelper config,
-            PhoneAccountHandle phoneAccount, Network network) {
+        PhoneAccountHandle phoneAccount, Network network) throws InitializingException {
         mContext = context;
         mPhoneAccount = phoneAccount;
         mNetwork = network;
@@ -120,6 +125,7 @@
         } catch (NumberFormatException e) {
             mConfig.handleEvent(OmtpEvents.DATA_INVALID_PORT);
             LogUtils.w(TAG, "Could not parse port number");
+            throw new InitializingException("cannot initialize ImapHelper:" + e.toString());
         }
 
         mQuotaOccupied = mPrefs
@@ -133,15 +139,6 @@
         mImapStore.closeConnection();
     }
 
-    /**
-     * If mImapStore is null, this means that there was a missing or badly formatted port number,
-     * which means there aren't sufficient credentials for login. If mImapStore is succcessfully
-     * initialized, then ImapHelper is ready to go.
-     */
-    public boolean isSuccessfullyInitialized() {
-        return mImapStore != null;
-    }
-
     public boolean isRoaming() {
         ConnectivityManager connectivityManager = (ConnectivityManager) mContext.getSystemService(
                 Context.CONNECTIVITY_SERVICE);
diff --git a/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java b/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java
index 6416cfb..5f89471 100644
--- a/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java
+++ b/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java
@@ -16,11 +16,11 @@
 
 package com.android.phone.vvm.omtp.protocol;
 
+import android.annotation.Nullable;
 import android.content.Context;
 import android.os.Bundle;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.SmsManager;
-
 import com.android.phone.vvm.omtp.ActivationTask;
 import com.android.phone.vvm.omtp.DefaultOmtpEventHandler;
 import com.android.phone.vvm.omtp.OmtpEvents;
@@ -82,4 +82,14 @@
             OmtpEvents event) {
         DefaultOmtpEventHandler.handleEvent(context, config, event);
     }
+
+    /**
+     * Given an VVM SMS with an unknown {@code event}, let the protocol attempt to translate it into
+     * an equivalent STATUS SMS. Returns {@code null} if it cannot be translated.
+     */
+    @Nullable
+    public Bundle translateStatusSmsBundle(OmtpVvmCarrierConfigHelper config, String event,
+            Bundle data) {
+        return null;
+    }
 }
diff --git a/src/com/android/phone/vvm/omtp/protocol/Vvm3EventHandler.java b/src/com/android/phone/vvm/omtp/protocol/Vvm3EventHandler.java
index 5a3c7aa..3a3adb8 100644
--- a/src/com/android/phone/vvm/omtp/protocol/Vvm3EventHandler.java
+++ b/src/com/android/phone/vvm/omtp/protocol/Vvm3EventHandler.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.telecom.PhoneAccountHandle;
 import android.util.Log;
-
 import com.android.phone.VoicemailStatus;
 import com.android.phone.settings.VoicemailChangePinActivity;
 import com.android.phone.vvm.omtp.DefaultOmtpEventHandler;
@@ -28,7 +27,6 @@
 import com.android.phone.vvm.omtp.OmtpEvents.Type;
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
 import com.android.phone.vvm.omtp.utils.PhoneAccountHandleConverter;
-
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
@@ -142,8 +140,14 @@
         switch (event) {
             case DATA_NO_CONNECTION:
             case DATA_NO_CONNECTION_CELLULAR_REQUIRED:
+            case DATA_ALL_SOCKET_CONNECTION_FAILED:
                 postError(context, config, VMS_NO_CELLULAR);
                 break;
+            case DATA_SSL_INVALID_HOST_NAME:
+            case DATA_CANNOT_ESTABLISH_SSL_SESSION:
+            case DATA_IOE_ON_OPEN:
+                postError(context, config, VMS_TIMEOUT);
+                break;
             case DATA_CANNOT_RESOLVE_HOST_ON_NETWORK:
                 postError(context, config, VMS_DNS_FAILURE);
                 break;
@@ -171,18 +175,11 @@
             case DATA_AUTH_USER_IS_BLOCKED:
                 postError(context, config, 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, config, IMAP_ERROR);
                 break;
-
             default:
                 return false;
         }
diff --git a/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java b/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java
index af424f5..c6dd434 100644
--- a/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java
+++ b/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java
@@ -22,6 +22,7 @@
 import android.os.Bundle;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.SmsManager;
+import android.text.TextUtils;
 
 import com.android.phone.common.mail.MessagingException;
 import com.android.phone.settings.VisualVoicemailSettingsUtil;
@@ -33,6 +34,7 @@
 import com.android.phone.vvm.omtp.VisualVoicemailPreferences;
 import com.android.phone.vvm.omtp.VvmLog;
 import com.android.phone.vvm.omtp.imap.ImapHelper;
+import com.android.phone.vvm.omtp.imap.ImapHelper.InitializingException;
 import com.android.phone.vvm.omtp.sms.OmtpMessageSender;
 import com.android.phone.vvm.omtp.sms.StatusMessage;
 import com.android.phone.vvm.omtp.sms.Vvm3MessageSender;
@@ -50,13 +52,18 @@
  */
 public class Vvm3Protocol extends VisualVoicemailProtocol {
 
-    private static String TAG = "Vvm3Protocol";
+    private static final String TAG = "Vvm3Protocol";
 
-    private static String IMAP_CHANGE_TUI_PWD_FORMAT = "CHANGE_TUI_PWD PWD=%1$s OLD_PWD=%2$s";
-    private static String IMAP_CHANGE_VM_LANG_FORMAT = "CHANGE_VM_LANG Lang=%1$s";
-    private static String IMAP_CLOSE_NUT = "CLOSE_NUT";
+    private static final String SMS_EVENT_UNRECOGNIZED = "UNRECOGNIZED";
+    private static final String SMS_EVENT_UNRECOGNIZED_CMD = "cmd";
+    private static final String SMS_EVENT_UNRECOGNIZED_STATUS = "STATUS";
+    private static final String DEFAULT_VMG_URL_KEY = "default_vmg_url";
 
-    private static String ISO639_Spanish = "es";
+    private static final String IMAP_CHANGE_TUI_PWD_FORMAT = "CHANGE_TUI_PWD PWD=%1$s OLD_PWD=%2$s";
+    private static final String IMAP_CHANGE_VM_LANG_FORMAT = "CHANGE_VM_LANG Lang=%1$s";
+    private static final String IMAP_CLOSE_NUT = "CLOSE_NUT";
+
+    private static final String ISO639_Spanish = "es";
 
     /**
      * For VVM3, if the STATUS SMS returns {@link StatusMessage#getProvisioningStatus()} of {@link
@@ -150,14 +157,40 @@
         return super.getCommand(command);
     }
 
+    @Override
+    public Bundle translateStatusSmsBundle(OmtpVvmCarrierConfigHelper config, String event,
+            Bundle data) {
+        // UNRECOGNIZED?cmd=STATUS is the response of a STATUS request when the user is provisioned
+        // with iPhone visual voicemail without VoLTE. Translate it into an unprovisioned status
+        // so provisioning can be done.
+        if (!SMS_EVENT_UNRECOGNIZED.equals(event)) {
+            return null;
+        }
+        if (!SMS_EVENT_UNRECOGNIZED_STATUS.equals(data.getString(SMS_EVENT_UNRECOGNIZED_CMD))) {
+            return null;
+        }
+        Bundle bundle = new Bundle();
+        bundle.putString(OmtpConstants.PROVISIONING_STATUS, OmtpConstants.SUBSCRIBER_UNKNOWN);
+        bundle.putString(OmtpConstants.RETURN_CODE,
+                VVM3_UNKNOWN_SUBSCRIBER_CAN_SUBSCRIBE_RESPONSE_CODE);
+        String vmgUrl = config.getString(DEFAULT_VMG_URL_KEY);
+        if (TextUtils.isEmpty(vmgUrl)) {
+            VvmLog.e(TAG, "Unable to translate STATUS SMS: VMG URL is not set in config");
+            return null;
+        }
+        bundle.putString(Vvm3Subscriber.VMG_URL_KEY, vmgUrl);
+        VvmLog.i(TAG, "UNRECOGNIZED?cmd=STATUS translated into unprovisioned STATUS SMS");
+        return bundle;
+    }
+
     private void startProvisionNewUser(PhoneAccountHandle phoneAccountHandle,
             OmtpVvmCarrierConfigHelper config, StatusMessage message) {
         try (NetworkWrapper wrapper = VvmNetworkRequest.getNetwork(config, phoneAccountHandle)) {
             Network network = wrapper.get();
 
             VvmLog.i(TAG, "new user: network available");
-            ImapHelper helper = new ImapHelper(config.getContext(), phoneAccountHandle, network);
-            try {
+            try (ImapHelper helper = new ImapHelper(config.getContext(), phoneAccountHandle,
+                network)) {
                 // VVM3 has inconsistent error language code to OMTP. Just issue a raw command
                 // here.
                 // TODO(b/29082671): use LocaleList
@@ -180,11 +213,9 @@
 
                     config.requestStatus();
                 }
-            } catch (MessagingException | IOException e) {
-                helper.handleEvent(OmtpEvents.VVM3_NEW_USER_SETUP_FAILED);
+            } catch (InitializingException | MessagingException | IOException e) {
+                config.handleEvent(OmtpEvents.VVM3_NEW_USER_SETUP_FAILED);
                 VvmLog.e(TAG, e.toString());
-            } finally {
-                helper.close();
             }
 
         }
diff --git a/src/com/android/phone/vvm/omtp/protocol/Vvm3Subscriber.java b/src/com/android/phone/vvm/omtp/protocol/Vvm3Subscriber.java
index a000527..7562275 100644
--- a/src/com/android/phone/vvm/omtp/protocol/Vvm3Subscriber.java
+++ b/src/com/android/phone/vvm/omtp/protocol/Vvm3Subscriber.java
@@ -26,15 +26,11 @@
 import android.text.Spanned;
 import android.text.style.URLSpan;
 import android.util.ArrayMap;
-
 import com.android.phone.Assert;
 import com.android.phone.vvm.omtp.ActivationTask;
-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.sms.StatusMessage;
-import com.android.phone.vvm.omtp.sms.StatusSmsFetcher;
 import com.android.phone.vvm.omtp.sync.VvmNetworkRequest;
 import com.android.phone.vvm.omtp.sync.VvmNetworkRequest.NetworkWrapper;
 import com.android.volley.AuthFailureError;
@@ -44,7 +40,6 @@
 import com.android.volley.toolbox.RequestFuture;
 import com.android.volley.toolbox.StringRequest;
 import com.android.volley.toolbox.Volley;
-
 import java.io.IOException;
 import java.net.CookieHandler;
 import java.net.CookieManager;
@@ -96,7 +91,7 @@
             + "  </MessageBody>"
             + "</VMGVVMRequest>";
 
-    private static final String VMG_URL_KEY = "vmg_url";
+    static final String VMG_URL_KEY = "vmg_url";
 
     // Self provisioning POST key/values. VVM3 API 2.1.0 12.3
     private static final String SPG_VZW_MDN_PARAM = "VZW_MDN";
@@ -232,36 +227,16 @@
         StringRequest stringRequest = new StringRequest(Request.Method.POST,
                 subscribeLink, future, future);
         mRequestQueue.add(stringRequest);
-        try (StatusSmsFetcher fetcher = new StatusSmsFetcher(mHelper.getContext(),
-                mHelper.getSubId())) {
-            try {
-                // A new STATUS SMS will be sent after this request.
-                future.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
-            } catch (TimeoutException e) {
-                mHelper.handleEvent(OmtpEvents.VVM3_SPG_CONNECTION_FAILED);
-                throw new ProvisioningException(e.toString());
-            }
-            Bundle data = fetcher.get();
-            StatusMessage message = new StatusMessage(data);
-            switch (message.getProvisioningStatus()) {
-                case OmtpConstants.SUBSCRIBER_READY:
-                    ActivationTask.updateSource(mHelper.getContext(), mHandle,
-                            mHelper.getSubId(), message);
-                    break;
-                case OmtpConstants.SUBSCRIBER_NEW:
-                    mHelper.getProtocol().startProvisioning(mTask, mHandle, mHelper, message, data);
-                    break;
-                default:
-                    mHelper.handleEvent(OmtpEvents.VVM3_SPG_CONNECTION_FAILED);
-                    throw new ProvisioningException("status is not ready or new after subscribed");
-            }
-        } catch (TimeoutException e) {
-            mHelper.handleEvent(OmtpEvents.CONFIG_STATUS_SMS_TIME_OUT);
-            throw new ProvisioningException("Timed out waiting for STATUS SMS after subscribed");
-        } catch (InterruptedException | ExecutionException | IOException e) {
+        try {
+            // A new STATUS SMS will be sent after this request.
+            future.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        } catch (TimeoutException | ExecutionException | InterruptedException e) {
             mHelper.handleEvent(OmtpEvents.VVM3_SPG_CONNECTION_FAILED);
             throw new ProvisioningException(e.toString());
         }
+        // It could take very long for the STATUS SMS to return. Waiting for it is unreliable.
+        // Just leave the CONFIG STATUS as CONFIGURING and end the task. The user can always
+        // manually retry if it took too long.
     }
 
     private String vvm3XmlRequest(String operation) throws ProvisioningException {
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
index 183d86d..7b4d2c3 100644
--- a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
@@ -25,12 +25,12 @@
 import android.provider.VoicemailContract;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.Voicemail;
-
 import com.android.phone.settings.VisualVoicemailSettingsUtil;
 import com.android.phone.vvm.omtp.ActivationTask;
 import com.android.phone.vvm.omtp.OmtpConstants;
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
 import com.android.phone.vvm.omtp.VvmLog;
+import com.android.phone.vvm.omtp.protocol.VisualVoicemailProtocol;
 import com.android.phone.vvm.omtp.sync.OmtpVvmSyncService;
 import com.android.phone.vvm.omtp.sync.SyncOneTask;
 import com.android.phone.vvm.omtp.sync.SyncTask;
@@ -48,12 +48,6 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        if (!UserManager.get(context).isUserUnlocked()) {
-            VvmLog.i(TAG, "Received message on locked device");
-            // A full sync will happen after the device is unlocked, so nothing need to be done.
-            return;
-        }
-
         mContext = context;
         int subId = intent.getExtras().getInt(VoicemailContract.EXTRA_VOICEMAIL_SMS_SUBID);
         PhoneAccountHandle phone = PhoneAccountHandleConverter.fromSubId(subId);
@@ -63,6 +57,15 @@
             return;
         }
 
+        if (!UserManager.get(context).isUserUnlocked()) {
+            VvmLog.i(TAG, "Received message on locked device");
+            // LegacyModeSmsHandler can handle new message notifications without storage access
+            LegacyModeSmsHandler.handle(context, intent, phone);
+            // A full sync will happen after the device is unlocked, so nothing else need to be
+            // done.
+            return;
+        }
+
         OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(mContext, subId);
         if (!VisualVoicemailSettingsUtil.isEnabled(mContext, phone)) {
             if (helper.isLegacyModeEnabled()) {
@@ -91,7 +94,17 @@
             // spontaneous send a STATUS SMS, in that case, the VVM service should be reactivated.
             ActivationTask.start(context, subId, data);
         } else {
-            VvmLog.e(TAG, "Unknown prefix: " + eventType);
+            VvmLog.w(TAG, "Unknown prefix: " + eventType);
+            VisualVoicemailProtocol protocol = helper.getProtocol();
+            if (protocol == null) {
+                return;
+            }
+            Bundle statusData = helper.getProtocol()
+                    .translateStatusSmsBundle(helper, eventType, data);
+            if (statusData != null) {
+                VvmLog.i(TAG, "Protocol recognized the SMS as STATUS, activating");
+                ActivationTask.start(context, subId, data);
+            }
         }
     }
 
@@ -107,6 +120,12 @@
         Intent serviceIntent = null;
         switch (message.getSyncTriggerEvent()) {
             case OmtpConstants.NEW_MESSAGE:
+                if (!OmtpConstants.VOICE.equals(message.getContentType())) {
+                    VvmLog.i(TAG, "Non-voice message of type '" + message.getContentType()
+                        + "' received, ignoring");
+                    return;
+                }
+
                 Voicemail.Builder builder = Voicemail.createForInsertion(
                         message.getTimestampMillis(), message.getSender())
                         .setPhoneAccount(phone)
diff --git a/src/com/android/phone/vvm/omtp/sms/StatusSmsFetcher.java b/src/com/android/phone/vvm/omtp/sms/StatusSmsFetcher.java
index 23abf9b..2c37dd9 100644
--- a/src/com/android/phone/vvm/omtp/sms/StatusSmsFetcher.java
+++ b/src/com/android/phone/vvm/omtp/sms/StatusSmsFetcher.java
@@ -24,10 +24,11 @@
 import android.content.IntentFilter;
 import android.os.Bundle;
 import android.provider.VoicemailContract;
-
 import com.android.phone.Assert;
 import com.android.phone.vvm.omtp.OmtpConstants;
-
+import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
+import com.android.phone.vvm.omtp.VvmLog;
+import com.android.phone.vvm.omtp.protocol.VisualVoicemailProtocol;
 import java.io.Closeable;
 import java.io.IOException;
 import java.util.concurrent.CompletableFuture;
@@ -40,6 +41,8 @@
  */
 public class StatusSmsFetcher extends BroadcastReceiver implements Closeable {
 
+    private static final String TAG = "VvmStatusSmsFetcher";
+
     private static final long STATUS_SMS_TIMEOUT_MILLIS = 60_000;
 
     private CompletableFuture<Bundle> mFuture = new CompletableFuture<>();
@@ -78,10 +81,28 @@
         String eventType = intent.getExtras()
                 .getString(VoicemailContract.EXTRA_VOICEMAIL_SMS_PREFIX);
 
-        if (!eventType.equals(OmtpConstants.STATUS_SMS_PREFIX)) {
+        if (eventType.equals(OmtpConstants.STATUS_SMS_PREFIX)) {
+            mFuture.complete(intent.getBundleExtra(VoicemailContract.EXTRA_VOICEMAIL_SMS_FIELDS));
             return;
         }
 
-        mFuture.complete(intent.getBundleExtra(VoicemailContract.EXTRA_VOICEMAIL_SMS_FIELDS));
+        if (eventType.equals(OmtpConstants.SYNC_SMS_PREFIX)) {
+            return;
+        }
+
+        VvmLog.i(TAG, "VVM SMS with event " + eventType
+                + " received, attempting to translate to STATUS SMS");
+        OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(context, subId);
+        VisualVoicemailProtocol protocol = helper.getProtocol();
+        if (protocol == null) {
+            return;
+        }
+        Bundle translatedBundle = protocol.translateStatusSmsBundle(helper, eventType,
+                intent.getBundleExtra(VoicemailContract.EXTRA_VOICEMAIL_SMS_FIELDS));
+
+        if (translatedBundle != null) {
+            VvmLog.i(TAG, "Translated to STATUS SMS");
+            mFuture.complete(translatedBundle);
+        }
     }
 }
diff --git a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
index 8abc2b3..2140e02 100644
--- a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
+++ b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
@@ -22,7 +22,6 @@
 import android.telecom.PhoneAccountHandle;
 import android.telecom.Voicemail;
 import android.text.TextUtils;
-
 import com.android.phone.PhoneUtils;
 import com.android.phone.settings.VisualVoicemailSettingsUtil;
 import com.android.phone.vvm.omtp.ActivationTask;
@@ -31,10 +30,10 @@
 import com.android.phone.vvm.omtp.VvmLog;
 import com.android.phone.vvm.omtp.fetch.VoicemailFetchedCallback;
 import com.android.phone.vvm.omtp.imap.ImapHelper;
+import com.android.phone.vvm.omtp.imap.ImapHelper.InitializingException;
 import com.android.phone.vvm.omtp.scheduling.BaseTask;
 import com.android.phone.vvm.omtp.sync.VvmNetworkRequest.NetworkWrapper;
 import com.android.phone.vvm.omtp.utils.PhoneAccountHandleConverter;
-
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -123,11 +122,6 @@
     private void doSync(BaseTask task, Network network, PhoneAccountHandle phoneAccount,
             Voicemail voicemail, String action) {
         try(ImapHelper imapHelper = new ImapHelper(mContext, phoneAccount, network)) {
-            if (!imapHelper.isSuccessfullyInitialized()) {
-                VvmLog.w(TAG, "Can't retrieve Imap credentials.");
-                return;
-            }
-
             boolean success;
             if (voicemail == null) {
                 success = syncAll(action, imapHelper, phoneAccount);
@@ -141,6 +135,9 @@
             } else {
                 task.fail();
             }
+        } catch (InitializingException e) {
+            VvmLog.w(TAG, "Can't retrieve Imap credentials.", e);
+            return;
         }
     }
 
diff --git a/src/com/android/services/telephony/ConferenceParticipantConnection.java b/src/com/android/services/telephony/ConferenceParticipantConnection.java
index 78f9ca3..3b8d89a 100644
--- a/src/com/android/services/telephony/ConferenceParticipantConnection.java
+++ b/src/com/android/services/telephony/ConferenceParticipantConnection.java
@@ -292,6 +292,10 @@
         sb.append(System.identityHashCode(this));
         sb.append(" endPoint:");
         sb.append(Log.pii(mEndpoint));
+        sb.append(" address:");
+        sb.append(Log.pii(getAddress()));
+        sb.append(" addressPresentation:");
+        sb.append(getAddressPresentation());
         sb.append(" parentConnection:");
         sb.append(Log.pii(mParentConnection.getAddress()));
         sb.append(" state:");
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index fcee589..2c60242 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -175,7 +175,10 @@
             Log.d(this, "onConnectionCapabilitiesChanged: Connection: %s," +
                     " connectionCapabilities: %s", c, connectionCapabilities);
             int capabilites = ImsConference.this.getConnectionCapabilities();
-            setConnectionCapabilities(applyHostCapabilities(capabilites, connectionCapabilities));
+            boolean isVideoConferencingSupported = mConferenceHost == null ? false :
+                    mConferenceHost.isCarrierVideoConferencingSupported();
+            setConnectionCapabilities(applyHostCapabilities(capabilites, connectionCapabilities,
+                    isVideoConferencingSupported));
         }
 
         @Override
@@ -277,7 +280,8 @@
         int capabilities = Connection.CAPABILITY_SUPPORT_HOLD | Connection.CAPABILITY_HOLD |
                 Connection.CAPABILITY_MUTE | Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN;
         capabilities = applyHostCapabilities(capabilities,
-                mConferenceHost.getConnectionCapabilities());
+                mConferenceHost.getConnectionCapabilities(),
+                mConferenceHost.isCarrierVideoConferencingSupported());
         setConnectionCapabilities(capabilities);
 
     }
@@ -287,24 +291,36 @@
      *
      * @param conferenceCapabilities The current conference capabilities.
      * @param capabilities The new conference host capabilities.
+     * @param isVideoConferencingSupported Whether video conferencing is supported.
      * @return The merged capabilities to be applied to the conference.
      */
-    private int applyHostCapabilities(int conferenceCapabilities, int capabilities) {
+    private int applyHostCapabilities(int conferenceCapabilities, int capabilities,
+            boolean isVideoConferencingSupported) {
+
         conferenceCapabilities = changeBitmask(conferenceCapabilities,
                     Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL,
                     can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL));
 
-        conferenceCapabilities = changeBitmask(conferenceCapabilities,
+        if (isVideoConferencingSupported) {
+            conferenceCapabilities = changeBitmask(conferenceCapabilities,
                     Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL,
                     can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL));
-
-        conferenceCapabilities = changeBitmask(conferenceCapabilities,
-                    Connection.CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO,
-                    can(capabilities, Connection.CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO));
-
-        conferenceCapabilities = changeBitmask(conferenceCapabilities,
+            conferenceCapabilities = changeBitmask(conferenceCapabilities,
                     Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO,
                     can(capabilities, Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO));
+        } else {
+            // If video conferencing is not supported, explicitly turn off the remote video
+            // capability and the ability to upgrade to video.
+            Log.v(this, "applyHostCapabilities : video conferencing not supported");
+            conferenceCapabilities = changeBitmask(conferenceCapabilities,
+                    Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL, false);
+            conferenceCapabilities = changeBitmask(conferenceCapabilities,
+                    Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO, false);
+        }
+
+        conferenceCapabilities = changeBitmask(conferenceCapabilities,
+                Connection.CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO,
+                can(capabilities, Connection.CAPABILITY_CANNOT_DOWNGRADE_VIDEO_TO_AUDIO));
 
         return conferenceCapabilities;
     }
@@ -572,7 +588,8 @@
         mConferenceHost.addConnectionListener(mConferenceHostListener);
         mConferenceHost.addTelephonyConnectionListener(mTelephonyConnectionListener);
         setConnectionCapabilities(applyHostCapabilities(getConnectionCapabilities(),
-                mConferenceHost.getConnectionCapabilities()));
+                mConferenceHost.getConnectionCapabilities(),
+                mConferenceHost.isCarrierVideoConferencingSupported()));
         setConnectionProperties(applyHostProperties(getConnectionProperties(),
                 mConferenceHost.getConnectionProperties()));
 
@@ -593,6 +610,8 @@
             return;
         }
 
+        Log.i(this, "handleConferenceParticipantsUpdate: size=%d", participants.size());
+
         // Perform the update in a synchronized manner.  It is possible for the IMS framework to
         // trigger two onConferenceParticipantsChanged callbacks in quick succession.  If the first
         // update adds new participants, and the second does something like update the status of one
@@ -605,7 +624,7 @@
 
             // Add any new participants and update existing.
             for (ConferenceParticipant participant : participants) {
-                Uri userEntity = participant.getHandle();
+                Uri userEntity = participant.getEndpoint();
 
                 participantUserEntities.add(userEntity);
                 if (!mConferenceParticipantConnections.containsKey(userEntity)) {
@@ -619,6 +638,8 @@
                 } else {
                     ConferenceParticipantConnection connection =
                             mConferenceParticipantConnections.get(userEntity);
+                    Log.i(this, "handleConferenceParticipantsUpdate: updateState, participant = %s",
+                            participant);
                     connection.updateState(participant.getState());
                 }
             }
@@ -628,7 +649,7 @@
                 // Set the state of the new participants at once and add to the conference
                 for (ConferenceParticipant newParticipant : newParticipants) {
                     ConferenceParticipantConnection connection =
-                            mConferenceParticipantConnections.get(newParticipant.getHandle());
+                            mConferenceParticipantConnections.get(newParticipant.getEndpoint());
                     connection.updateState(newParticipant.getState());
                 }
             }
@@ -679,12 +700,11 @@
         connection.addConnectionListener(mParticipantListener);
         connection.setConnectTimeMillis(parent.getConnectTimeMillis());
 
-        if (Log.VERBOSE) {
-            Log.v(this, "createConferenceParticipantConnection: %s", connection);
-        }
+        Log.i(this, "createConferenceParticipantConnection: participant=%s, connection=%s",
+                participant, connection);
 
         synchronized(mUpdateSyncRoot) {
-            mConferenceParticipantConnections.put(participant.getHandle(), connection);
+            mConferenceParticipantConnections.put(participant.getEndpoint(), connection);
         }
         mTelephonyConnectionService.addExistingConnection(mConferenceHostPhoneAccountHandle,
                 connection);
@@ -697,7 +717,7 @@
      * @param participant The participant to remove.
      */
     private void removeConferenceParticipant(ConferenceParticipantConnection participant) {
-        Log.d(this, "removeConferenceParticipant: %s", participant);
+        Log.i(this, "removeConferenceParticipant: %s", participant);
 
         participant.removeConnectionListener(mParticipantListener);
         synchronized(mUpdateSyncRoot) {
@@ -805,32 +825,34 @@
 
         if (originalConnection != null &&
                 originalConnection.getPhoneType() != PhoneConstants.PHONE_TYPE_IMS) {
-            if (Log.VERBOSE) {
-                Log.v(this,
-                        "Original connection for conference host is no longer an IMS connection; " +
-                                "new connection: %s", originalConnection);
-            }
+            Log.i(this,
+                    "handleOriginalConnectionChange : handover from IMS connection to " +
+                            "new connection: %s", originalConnection);
 
             PhoneAccountHandle phoneAccountHandle = null;
             if (mConferenceHost.getPhone() != null) {
                 if (mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_IMS) {
                     Phone imsPhone = mConferenceHost.getPhone();
-                    // The phone account handle for an ImsPhone is based on the default phone (ie the
-                    // base GSM or CDMA phone, not on the ImsPhone itself).
+                    // The phone account handle for an ImsPhone is based on the default phone (ie
+                    // the base GSM or CDMA phone, not on the ImsPhone itself).
                     phoneAccountHandle =
                             PhoneUtils.makePstnPhoneAccountHandle(imsPhone.getDefaultPhone());
                 } else {
-                    // In the case of SRVCC, we still need a phone account, so use the top level phone
-                    // to create a phone account.
+                    // In the case of SRVCC, we still need a phone account, so use the top level
+                    // phone to create a phone account.
                     phoneAccountHandle = PhoneUtils.makePstnPhoneAccountHandle(
                             mConferenceHost.getPhone());
                 }
             }
 
             if (mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
+                Log.i(this,"handleOriginalConnectionChange : SRVCC to GSM");
                 GsmConnection c = new GsmConnection(originalConnection, getTelecomCallId());
                 // This is a newly created conference connection as a result of SRVCC
                 c.setConferenceSupported(true);
+                c.addCapability(Connection.CAPABILITY_CONFERENCE_HAS_NO_CHILDREN);
+                c.setConnectionProperties(
+                        c.getConnectionProperties() | Connection.PROPERTY_IS_DOWNGRADED_CONFERENCE);
                 c.updateState();
                 // Copy the connect time from the conferenceHost
                 c.setConnectTimeMillis(mConferenceHost.getConnectTimeMillis());
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 7354b10..d931f32 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -235,6 +235,13 @@
     };
 
     /**
+     * @return {@code true} if carrier video conferencing is supported, {@code false} otherwise.
+     */
+    public boolean isCarrierVideoConferencingSupported() {
+        return mIsCarrierVideoConferencingSupported;
+    }
+
+    /**
      * A listener/callback mechanism that is specific communication from TelephonyConnections
      * to TelephonyConnectionService (for now). It is more specific that Connection.Listener
      * because it is only exposed in Telephony.
@@ -412,6 +419,13 @@
     private boolean mIsConferenceSupported;
 
     /**
+     * Indicates whether the carrier supports video conferencing; captures the current state of the
+     * carrier config
+     * {@link android.telephony.CarrierConfigManager#KEY_SUPPORT_VIDEO_CONFERENCE_CALL_BOOL}.
+     */
+    private boolean mIsCarrierVideoConferencingSupported;
+
+    /**
      * Indicates whether or not this connection has CDMA Enhanced Voice Privacy enabled.
      */
     private boolean mIsCdmaVoicePrivacyEnabled;
@@ -1515,15 +1529,15 @@
                 .getInstance(getPhone().getContext());
         boolean isConferencingSupported = telecomAccountRegistry
                 .isMergeCallSupported(phoneAccountHandle);
-        boolean isVideoConferencingSupported = telecomAccountRegistry
+        mIsCarrierVideoConferencingSupported = telecomAccountRegistry
                 .isVideoConferencingSupported(phoneAccountHandle);
         boolean isMergeOfWifiCallsAllowedWhenVoWifiOff = telecomAccountRegistry
                 .isMergeOfWifiCallsAllowedWhenVoWifiOff(phoneAccountHandle);
 
         Log.v(this, "refreshConferenceSupported : isConfSupp=%b, isVidConfSupp=%b, " +
                 "isMergeOfWifiAllowed=%b, isWifi=%b, isVoWifiEnabled=%b", isConferencingSupported,
-                isVideoConferencingSupported, isMergeOfWifiCallsAllowedWhenVoWifiOff, isWifi(),
-                isVoWifiEnabled);
+                mIsCarrierVideoConferencingSupported, isMergeOfWifiCallsAllowedWhenVoWifiOff,
+                isWifi(), isVoWifiEnabled);
         boolean isConferenceSupported = true;
         if (mTreatAsEmergencyCall) {
             isConferenceSupported = false;
@@ -1531,7 +1545,7 @@
         } else if (!isConferencingSupported) {
             isConferenceSupported = false;
             Log.d(this, "refreshConferenceSupported = false; carrier doesn't support conf.");
-        } else if (isVideoCall && !isVideoConferencingSupported) {
+        } else if (isVideoCall && !mIsCarrierVideoConferencingSupported) {
             isConferenceSupported = false;
             Log.d(this, "refreshConferenceSupported = false; video conf not supported.");
         } else if (!isMergeOfWifiCallsAllowedWhenVoWifiOff && isWifi() && !isVoWifiEnabled) {