Use telecom.Connection state for WFC StatusHint.
automerge: 6eaa6db

* commit '6eaa6db73229c55e4abd610822e857b0a4cc684f':
  Use telecom.Connection state for WFC StatusHint.
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index bc84bf5..a0802fd 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -98,6 +98,10 @@
     <uses-permission android:name="android.permission.REGISTER_CALL_PROVIDER" />
     <uses-permission android:name="android.permission.REGISTER_SIM_SUBSCRIPTION" />
     <uses-permission android:name="android.permission.BIND_CARRIER_MESSAGING_SERVICE" />
+    <uses-permission android:name="android.permission.READ_SYNC_SETTINGS" />
+    <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" />
+    <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
+    <uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
 
     <!-- This tells the activity manager to not delay any of our activity
          start requests, even if they happen immediately after the user
@@ -501,6 +505,12 @@
                 android:uiOptions="splitActionBarWhenNarrow">
         </activity>
 
+        <activity android:name="ImsEditor"
+                android:theme="@style/DialerSettingsLight"
+                android:configChanges="orientation|screenSize|keyboardHidden"
+                android:uiOptions="splitActionBarWhenNarrow">
+        </activity>
+
         <!-- End SIP -->
 
         <activity android:name="ErrorDialogActivity"
@@ -583,5 +593,31 @@
                 <action android:name="android.content.action.SEARCH_INDEXABLES_PROVIDER" />
             </intent-filter>
         </provider>
+        <receiver android:name="com.android.phone.vvm.omtp.sms.OmtpMessageReceiver"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.DATA_SMS_RECEIVED" />
+                <data android:scheme="sms" />
+            </intent-filter>
+        </receiver>
+        <service
+                android:name="com.android.phone.vvm.omtp.OmtpVvmSyncService"
+                android:exported="true"
+                android:process=":sync">
+            <intent-filter>
+                <action android:name="android.content.SyncAdapter"/>
+            </intent-filter>
+            <meta-data android:name="android.content.SyncAdapter"
+                    android:resource="@xml/syncadapter" />
+        </service>
+        <service
+            android:name="android.telecom.AuthenticatorService">
+        <intent-filter>
+            <action android:name="android.accounts.AccountAuthenticator"/>
+        </intent-filter>
+        <meta-data
+            android:name="android.accounts.AccountAuthenticator"
+            android:resource="@xml/authenticator" />
+       </service>
     </application>
 </manifest>
diff --git a/res/values/array.xml b/res/values/array.xml
new file mode 100644
index 0000000..79b6034
--- /dev/null
+++ b/res/values/array.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<!-- Array resources for the Phone app. -->
+<resources>
+    <!-- Array resource for IMS VT call quality entries-->
+    <string-array translatable="true" name="ims_vt_call_quality_entries">
+        <item>@string/ims_vt_call_quality_low</item>
+        <item>@string/ims_vt_call_quality_high</item>
+    </string-array>
+
+    <!-- Array resource for IMS VT call quality values-->
+    <string-array translatable="false" name="ims_vt_call_quality_values">
+        <item>0</item>
+        <item>1</item>
+    </string-array>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index abeeee0..4118e0a 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -753,7 +753,8 @@
     <string name="tty_mode_option_summary">Set TTY mode</string>
     <string name="auto_retry_mode_title">Auto-retry</string>
     <string name="auto_retry_mode_summary">Enable Auto-retry mode</string>
-
+    <!-- TTY Mode change is NOT allowed during a video call -->
+    <string name="tty_mode_not_allowed_video_call">TTY Mode change is not allowed during a video call</string>
     <!-- FDN list screen: menu item label -->
     <string name="menu_add">Add contact</string>
     <!-- FDN list screen: menu item label -->
@@ -1298,4 +1299,17 @@
     <!-- DO NOT TRANSLATE. Internal key for a voicemail notification preference. -->
     <string name="wifi_calling_settings_key">button_wifi_calling_settings_key</string>
 
+    <!-- IMS settings related strings -->
+    <!-- Title of the ims editor screen. [CHAR LIMIT=NONE] -->
+    <string name="ims_edit_title">IMS account details</string>
+    <!-- Title displayed IMS account settings in the sip settings category. [CHAR LIMIT=NONE] -->
+    <string name="ims_accounts_title">IMS Account</string>
+
+    <!-- VT call quality settings. -->
+    <string translatable="false" name="ims_vt_call_quality">VtCallQuality</string>
+    <string name="ims_vt_call_quality_title">VT Call Quality</string>
+    <string name="ims_vt_call_quality_low">Low</string>
+    <string name="ims_vt_call_quality_high">High</string>
+    <string name="ims_vt_call_quality_unknown">Unknown</string>
+    <string name="ims_vt_call_quality_set_failed">Failed to set video quality</string>
 </resources>
diff --git a/res/xml/authenticator.xml b/res/xml/authenticator.xml
new file mode 100644
index 0000000..f035d3a
--- /dev/null
+++ b/res/xml/authenticator.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+<account-authenticator
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:accountType="com.android.phone.vvm.omtp" />
\ No newline at end of file
diff --git a/res/xml/call_feature_setting.xml b/res/xml/call_feature_setting.xml
index 543fae5..4af9736 100644
--- a/res/xml/call_feature_setting.xml
+++ b/res/xml/call_feature_setting.xml
@@ -149,4 +149,14 @@
 
     </PreferenceScreen>
 
+    <PreferenceScreen
+        android:key="ims_account_settings_key"
+        android:title="@string/ims_accounts_title"
+        android:persistent="false">
+
+        <intent android:action="android.intent.action.MAIN"
+            android:targetPackage="com.android.phone"
+            android:targetClass="com.android.phone.ImsEditor" />
+    </PreferenceScreen>
+
 </PreferenceScreen>
diff --git a/res/xml/ims_edit.xml b/res/xml/ims_edit.xml
new file mode 100644
index 0000000..f16e2f5
--- /dev/null
+++ b/res/xml/ims_edit.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+        android:title="@string/ims_edit_title"
+        android:persistent="false">
+
+    <ListPreference
+        android:key="@string/ims_vt_call_quality"
+        android:title="@string/ims_vt_call_quality_title"
+        android:entries="@array/ims_vt_call_quality_entries"
+        android:entryValues="@array/ims_vt_call_quality_values"
+        android:summary="@string/ims_vt_call_quality_unknown"
+        android:persistent="false"
+        android:enabled="true"
+        android:dialogTitle="@string/ims_vt_call_quality_title"/>
+
+</PreferenceScreen>
diff --git a/res/xml/syncadapter.xml b/res/xml/syncadapter.xml
new file mode 100644
index 0000000..b0484b1
--- /dev/null
+++ b/res/xml/syncadapter.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 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.
+-->
+<sync-adapter
+        xmlns:android="http://schemas.android.com/apk/res/android"
+        android:contentAuthority="com.android.voicemail"
+        android:accountType="com.android.phone.vvm.omtp"
+        android:userVisible="false"
+        android:supportsUploading="true"
+        android:allowParallelSyncs="false"
+        android:isAlwaysSyncable="true"/>
\ No newline at end of file
diff --git a/sip/src/com/android/services/telephony/sip/SipUtil.java b/sip/src/com/android/services/telephony/sip/SipUtil.java
index ea5ea46..7e94804 100644
--- a/sip/src/com/android/services/telephony/sip/SipUtil.java
+++ b/sip/src/com/android/services/telephony/sip/SipUtil.java
@@ -105,7 +105,15 @@
      * @return The PhoneAccount.
      */
     static PhoneAccount createPhoneAccount(Context context, SipProfile profile) {
+        // Build a URI to represent the SIP account.  Does not use SipProfile#getUriString() since
+        // that prototype can include transport information which we do not want to see in the
+        // phone account.
+        String sipAddress = profile.getUserName() + "@" + profile.getSipDomain();
+        Uri sipUri = Uri.parse(PhoneAccount.SCHEME_SIP + ":" + sipAddress);
 
+        // SipProfile#getUriString() is used here for legacy reasons. Changing to use the sipAddress
+        // defined above would cause SIP profiles with a TCP transport to be duplicated in the
+        // PhoneAccountRegistry since the ID would change on re-registration.
         PhoneAccountHandle accountHandle =
                 SipUtil.createAccountHandle(context, profile.getUriString());
 
@@ -118,8 +126,8 @@
         PhoneAccount.Builder builder = PhoneAccount.builder(accountHandle, profile.getDisplayName())
                 .setCapabilities(PhoneAccount.CAPABILITY_CALL_PROVIDER
                         | PhoneAccount.CAPABILITY_MULTI_USER)
-                .setAddress(Uri.parse(profile.getUriString()))
-                .setShortDescription(profile.getDisplayName())
+                .setAddress(sipUri)
+                .setShortDescription(sipAddress)
                 .setIcon(context, R.drawable.ic_dialer_sip_black_24dp)
                 .setSupportedUriSchemes(supportedUriSchemes);
 
diff --git a/src/com/android/phone/CallFeaturesSetting.java b/src/com/android/phone/CallFeaturesSetting.java
index c211323..4fa28ab 100644
--- a/src/com/android/phone/CallFeaturesSetting.java
+++ b/src/com/android/phone/CallFeaturesSetting.java
@@ -336,6 +336,10 @@
         } else if (preference == mButtonDTMF) {
             return true;
         } else if (preference == mButtonTTY) {
+            if(mPhone.isVideoCallPresent()) {
+                // TTY Mode change is not allowed during a VT call
+                showDialogIfForeground(VoicemailDialogUtil.TTY_SET_RESPONSE_ERROR);
+            }
             return true;
         } else if (preference == mButtonAutoRetry) {
             android.provider.Settings.Global.putInt(mPhone.getContext().getContentResolver(),
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 4037f6d..9369b6a 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -35,6 +35,7 @@
 import android.os.UserHandle;
 import android.preference.PreferenceManager;
 import android.provider.Settings;
+import android.telecom.PhoneAccount;
 import android.telephony.CellInfo;
 import android.telephony.IccOpenLogicalChannelResponse;
 import android.telephony.NeighboringCellInfo;
@@ -2256,10 +2257,16 @@
      * {@hide}
      * Returns the IMS Registration Status
      */
+    @Override
     public boolean isImsRegistered() {
         return mPhone.isImsRegistered();
     }
 
+    @Override
+    public int getSubIdForPhoneAccount(PhoneAccount phoneAccount) {
+        return PhoneUtils.getSubIdForPhoneAccount(phoneAccount);
+    }
+
     /*
      * {@hide}
      * Returns the IMS Registration Status
diff --git a/src/com/android/phone/PhoneUtils.java b/src/com/android/phone/PhoneUtils.java
index 6b34208..f7c5939 100644
--- a/src/com/android/phone/PhoneUtils.java
+++ b/src/com/android/phone/PhoneUtils.java
@@ -36,6 +36,7 @@
 import android.telecom.PhoneAccountHandle;
 import android.telecom.VideoProfile;
 import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionManager;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
@@ -60,6 +61,7 @@
 import com.android.internal.telephony.TelephonyCapabilities;
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.telephony.sip.SipPhone;
+import com.android.internal.telephony.uicc.UiccCard;
 import com.android.phone.CallGatewayManager.RawGatewayInfo;
 import com.android.services.telephony.TelephonyConnectionService;
 
@@ -2447,20 +2449,58 @@
                 == Configuration.ORIENTATION_LANDSCAPE;
     }
 
+    public static PhoneAccountHandle makePstnPhoneAccountHandle(int phoneId) {
+        return makePstnPhoneAccountHandle(PhoneFactory.getPhone(phoneId));
+    }
+
     public static PhoneAccountHandle makePstnPhoneAccountHandle(Phone phone) {
         return makePstnPhoneAccountHandleWithPrefix(phone, "", false);
     }
 
     public static PhoneAccountHandle makePstnPhoneAccountHandleWithPrefix(
             Phone phone, String prefix, boolean isEmergency) {
-        ComponentName pstnConnectionServiceName =
-                new ComponentName(phone.getContext(), TelephonyConnectionService.class);
+        ComponentName pstnConnectionServiceName = getPstnConnectionServiceName();
         // TODO: Should use some sort of special hidden flag to decorate this account as
         // an emergency-only account
-        String id = isEmergency ? "E" : prefix + String.valueOf(phone.getSubId());
+        String id = isEmergency ? "E" : prefix + String.valueOf(phone.getIccSerialNumber());
         return new PhoneAccountHandle(pstnConnectionServiceName, id);
     }
 
+    public static int getSubIdForPhoneAccount(PhoneAccount phoneAccount) {
+        if (phoneAccount != null
+                && phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
+            return getSubIdForPhoneAccountHandle(phoneAccount.getAccountHandle());
+        }
+        return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    }
+
+    public static int getSubIdForPhoneAccountHandle(PhoneAccountHandle handle) {
+        if (handle != null && handle.getComponentName().equals(getPstnConnectionServiceName())) {
+            Phone phone = getPhoneFromIccId(handle.getId());
+            if (phone != null) {
+                return phone.getSubId();
+            }
+        }
+        return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
+    }
+
+    private static ComponentName getPstnConnectionServiceName() {
+        return new ComponentName(PhoneGlobals.getInstance(), TelephonyConnectionService.class);
+    }
+
+    private static Phone getPhoneFromIccId(String iccId) {
+        if (!TextUtils.isEmpty(iccId)) {
+            for (Phone phone : PhoneFactory.getPhones()) {
+                String phoneIccId = phone.getIccSerialNumber();
+                if (iccId.equals(phoneIccId)) {
+                    return phone;
+                }
+            }
+        }
+
+        return null;
+    }
+
     /**
      * Register ICC status for all phones.
      */
diff --git a/src/com/android/phone/ims/ImsEditor.java b/src/com/android/phone/ims/ImsEditor.java
new file mode 100644
index 0000000..bbd4526
--- /dev/null
+++ b/src/com/android/phone/ims/ImsEditor.java
@@ -0,0 +1,170 @@
+/* Copyright (C) 2015 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;
+
+import android.os.Bundle;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.telephony.SubscriptionManager;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.android.ims.ImsConfig;
+import com.android.ims.ImsConfigListener;
+import com.android.ims.ImsException;
+import com.android.ims.ImsManager;
+import com.android.phone.R;
+
+/**
+ * The activity class for editing a new or existing IMS profile.
+ */
+public class ImsEditor extends PreferenceActivity
+        implements Preference.OnPreferenceChangeListener {
+
+    private static final String TAG = ImsEditor.class.getSimpleName();
+
+    private ListPreference mVideoCallQuality;
+    private ImsConfig mImsConfig;
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        Log.d(TAG, "onResume");
+        getVideoQuality();
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.v(TAG, "start profile editor");
+        super.onCreate(savedInstanceState);
+
+        addPreferencesFromResource(R.xml.ims_edit);
+        PreferenceScreen screen = getPreferenceScreen();
+
+        mVideoCallQuality = (ListPreference) screen
+                .findPreference(getString(R.string.ims_vt_call_quality));
+        mVideoCallQuality.setOnPreferenceChangeListener(this);
+
+        try {
+            ImsManager imsManager = ImsManager.getInstance(getBaseContext(),
+                    SubscriptionManager.getDefaultVoiceSubId());
+            mImsConfig = imsManager.getConfigInterface();
+        } catch (ImsException e) {
+            mImsConfig = null;
+            Log.e(TAG, "ImsService is not running");
+        }
+    }
+
+    private ImsConfigListener imsConfigListener = new ImsConfigListener.Stub() {
+        public void onGetVideoQuality(int status, int quality) {
+            if (hasRequestFailed(status)) {
+                quality = ImsConfig.OperationValuesConstants.VIDEO_QUALITY_UNKNOWN;
+                Log.e(TAG, "onGetVideoQuality: failed. errorCode = " + status);
+            }
+            Log.d(TAG, "onGetVideoQuality: value = " + quality);
+            loadVideoCallQualityPrefs(quality);
+        }
+
+        public void onSetVideoQuality(int status) {
+            if (hasRequestFailed(status)) {
+                Log.e(TAG, "onSetVideoQuality: set failed. errorCode = " + status);
+                Toast.makeText(getApplicationContext(), R.string.ims_vt_call_quality_set_failed,
+                        Toast.LENGTH_SHORT).show();
+                getVideoQuality(); // Set request failed, get current value.
+            } else {
+                Log.d(TAG, "onSetVideoQuality: set succeeded.");
+            }
+        }
+
+        public void onGetFeatureResponse(int feature, int network, int value, int status) {
+            //TODO not required as of now
+        }
+
+        public void onSetFeatureResponse(int feature, int network, int value, int status) {
+            //TODO not required as of now
+        }
+    };
+
+    @Override
+    public boolean onPreferenceChange(Preference pref, Object newValue) {
+        if (pref.equals(mVideoCallQuality)) {
+            if (newValue == null) {
+                Log.e(TAG, "onPreferenceChange invalid value received");
+            } else {
+                final int quality = Integer.parseInt(newValue.toString());
+                boolean result = setVideoQuality(quality);
+                if (result) {
+                    loadVideoCallQualityPrefs(quality);
+                }
+                return result;
+            }
+        }
+        return true;
+    }
+
+    private void loadVideoCallQualityPrefs(int vqValue) {
+        Log.d(TAG, "loadVideoCallQualityPrefs, vqValue = " + vqValue);
+        final String videoQuality = videoQualityToString(vqValue);
+        mVideoCallQuality.setValue(String.valueOf(vqValue));
+        mVideoCallQuality.setSummary(videoQuality);
+    }
+
+    private void getVideoQuality() {
+        try {
+            if (mImsConfig != null) {
+                mImsConfig.getVideoQuality(imsConfigListener);
+            } else {
+                loadVideoCallQualityPrefs(ImsConfig.OperationValuesConstants.VIDEO_QUALITY_UNKNOWN);
+                Log.e(TAG, "getVideoQuality failed. mImsConfig is null");
+            }
+        } catch (ImsException e) {
+            Log.e(TAG, "getVideoQuality failed. Exception = " + e);
+        }
+    }
+
+    private boolean setVideoQuality(int quality) {
+        try {
+            if (mImsConfig != null) {
+                mImsConfig.setVideoQuality(quality, imsConfigListener);
+            } else {
+                Log.e(TAG, "setVideoQuality failed. mImsConfig is null");
+                return false;
+            }
+        } catch (ImsException e) {
+            Log.e(TAG, "setVideoQuality failed. Exception = " + e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean hasRequestFailed(int result) {
+        return (result != ImsConfig.OperationStatusConstants.SUCCESS);
+    }
+
+    private String videoQualityToString(int quality) {
+        switch (quality) {
+            case ImsConfig.OperationValuesConstants.VIDEO_QUALITY_HIGH:
+                return getString(R.string.ims_vt_call_quality_high);
+            case ImsConfig.OperationValuesConstants.VIDEO_QUALITY_LOW:
+                return getString(R.string.ims_vt_call_quality_low);
+            case ImsConfig.OperationValuesConstants.VIDEO_QUALITY_UNKNOWN:
+            default:
+                return getString(R.string.ims_vt_call_quality_unknown);
+        }
+    }
+}
diff --git a/src/com/android/phone/settings/VoicemailDialogUtil.java b/src/com/android/phone/settings/VoicemailDialogUtil.java
index 0a9e4bc..f1e7fa7 100644
--- a/src/com/android/phone/settings/VoicemailDialogUtil.java
+++ b/src/com/android/phone/settings/VoicemailDialogUtil.java
@@ -35,11 +35,12 @@
     public static final int VM_FWD_SAVING_DIALOG = 601;
     public static final int VM_FWD_READING_DIALOG = 602;
     public static final int VM_REVERTING_DIALOG = 603;
+    public static final int TTY_SET_RESPONSE_ERROR = 800;
 
     public static Dialog getDialog(CallFeaturesSetting parent, int id) {
         if ((id == VM_RESPONSE_ERROR_DIALOG) || (id == VM_NOCHANGE_ERROR_DIALOG) ||
             (id == FWD_SET_RESPONSE_ERROR_DIALOG) || (id == FWD_GET_RESPONSE_ERROR_DIALOG) ||
-                (id == VM_CONFIRM_DIALOG)) {
+                (id == VM_CONFIRM_DIALOG) || (id == TTY_SET_RESPONSE_ERROR)) {
 
             AlertDialog.Builder b = new AlertDialog.Builder(parent);
 
@@ -75,6 +76,12 @@
                     b.setPositiveButton(R.string.alert_dialog_yes, parent);
                     b.setNegativeButton(R.string.alert_dialog_no, parent);
                     break;
+                case TTY_SET_RESPONSE_ERROR:
+                    titleId = R.string.tty_mode_option_title;
+                    msgId = R.string.tty_mode_not_allowed_video_call;
+                    b.setIconAttribute(android.R.attr.alertDialogIcon);
+                    b.setPositiveButton(R.string.ok, parent);
+                    break;
                 default:
                     msgId = R.string.exception_error;
                     // Set Button 3, tells the activity that the error is
diff --git a/src/com/android/phone/vvm/omtp/OmtpConstants.java b/src/com/android/phone/vvm/omtp/OmtpConstants.java
new file mode 100644
index 0000000..971d2d6
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/OmtpConstants.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2015 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 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.
+ */
+public class OmtpConstants {
+    public static final String SMS_FIELD_SEPARATOR = ";";
+    public static final String SMS_KEY_VALUE_SEPARATOR = "=";
+    public static final String SMS_PREFIX_SEPARATOR = ":";
+
+    public static final String CLIENT_PREFIX = "//VVM";
+    public static final String SYNC_SMS_PREFIX = CLIENT_PREFIX + ":SYNC:";
+    public static final String STATUS_SMS_PREFIX = CLIENT_PREFIX + ":STATUS:";
+
+    // This is the format designated by the OMTP spec.
+    public static final String DATE_TIME_FORMAT = "dd/MM/yyyy HH:mm Z";
+
+    /** OMTP protocol versions. */
+    public static final String PROTOCOL_VERSION1_1 = "11";
+    public static final String PROTOCOL_VERSION1_2 = "12";
+    public static final String PROTOCOL_VERSION1_3 = "13";
+
+    ///////////////////////// Client/Mobile originated SMS //////////////////////
+
+    /** Mobile Originated requests */
+    public static final String ACTIVATE_REQUEST = "Activate";
+    public static final String DEACTIVATE_REQUEST = "Deactivate";
+    public static final String STATUS_REQUEST = "Status";
+
+    /** fields that can be present in a Mobile Originated OMTP SMS */
+    public static final String CLIENT_TYPE = "ct";
+    public static final String APPLICATION_PORT = "pt";
+    public static final String PROTOCOL_VERSION = "pv";
+
+
+    //////////////////////////////// Sync SMS fields ////////////////////////////
+
+    /**
+     * Sync SMS fields.
+     * <p>
+     * Each string constant is the field's key in the SMS body which is used by the parser to
+     * identify the field's value, if present, in the SMS body.
+     */
+
+    /**
+     * The event that triggered this SYNC SMS.
+     * See {@link OmtpConstants#SYNC_TRIGGER_EVENT_VALUES}
+     */
+    public static final String SYNC_TRIGGER_EVENT = "ev";
+    public static final String MESSAGE_UID = "id";
+    public static final String MESSAGE_LENGTH = "l";
+    public static final String NUM_MESSAGE_COUNT = "c";
+    /** See {@link OmtpConstants#CONTENT_TYPE_VALUES} */
+    public static final String CONTENT_TYPE = "t";
+    public static final String SENDER = "s";
+    public static final String TIME = "dt";
+
+    /**
+     * SYNC message trigger events.
+     * <p>
+     * These are the possible values of {@link OmtpConstants#SYNC_TRIGGER_EVENT}.
+     */
+    public static final String NEW_MESSAGE = "NM";
+    public static final String MAILBOX_UPDATE = "MBU";
+    public static final String GREETINGS_UPDATE = "GU";
+
+    public static final String[] SYNC_TRIGGER_EVENT_VALUES = {
+        NEW_MESSAGE,
+        MAILBOX_UPDATE,
+        GREETINGS_UPDATE
+    };
+
+    /**
+     * Content types supported by OMTP VVM.
+     * <p>
+     * These are the possible values of {@link OmtpConstants#CONTENT_TYPE}.
+     */
+    public static final String VOICE = "v";
+    public static final String VIDEO = "o";
+    public static final String FAX = "f";
+    /** Voice message deposited by an external application */
+    public static final String INFOTAINMENT = "i";
+    /** Empty Call Capture - i.e. voicemail with no voice message. */
+    public static final String ECC = "e";
+
+    public static final String[] CONTENT_TYPE_VALUES = {VOICE, VIDEO, FAX, INFOTAINMENT, ECC};
+
+    ////////////////////////////// Status SMS fields ////////////////////////////
+
+    /**
+     * Status SMS fields.
+     * <p>
+     * Each string constant is the field's key in the SMS body which is used by the parser to
+     * identify the field's value, if present, in the SMS body.
+     */
+    /** See {@link OmtpConstants#PROVISIONING_STATUS_VALUES} */
+    public static final String PROVISIONING_STATUS = "st";
+    /** See {@link OmtpConstants#RETURN_CODE_VALUES} */
+    public static final String RETURN_CODE = "rc";
+    /** URL to send users to for activation VVM */
+    public static final String SUBSCRIPTION_URL = "rs";
+    /** IMAP4/SMTP server IP address or fully qualified domain name */
+    public static final String SERVER_ADDRESS = "srv";
+    /** Phone number to access voicemails through Telephony User Interface */
+    public static final String TUI_ACCESS_NUMBER = "tui";
+    /** Number to send client origination SMS */
+    public static final String CLIENT_SMS_DESTINATION_NUMBER = "dn";
+    public static final String IMAP_PORT = "ipt";
+    public static final String IMAP_USER_NAME = "u";
+    public static final String IMAP_PASSWORD = "pw";
+    public static final String SMTP_PORT = "spt";
+    public static final String SMTP_USER_NAME = "smtp_u";
+    public static final String SMTP_PASSWORD = "smtp_pw";
+
+    /**
+     * User provisioning status values.
+     * <p>
+     * Referred by {@link OmtpConstants#PROVISIONING_STATUS}.
+     */
+    // TODO: As per the spec the code could be either be with or w/o quotes  = "N"/N). Currently
+    // this only handles the w/o quotes values.
+    public static final String SUBSCRIBER_NEW = "N";
+    public static final String SUBSCRIBER_READY = "R";
+    public static final String SUBSCRIBER_PROVISIONED = "P";
+    public static final String SUBSCRIBER_UNKNOWN = "U";
+    public static final String SUBSCRIBER_BLOCKED = "B";
+
+    public static final String[] PROVISIONING_STATUS_VALUES = {
+        SUBSCRIBER_NEW,
+        SUBSCRIBER_READY,
+        SUBSCRIBER_PROVISIONED,
+        SUBSCRIBER_UNKNOWN,
+        SUBSCRIBER_BLOCKED
+    };
+
+    /**
+     * The return code included in a status message.
+     * <p>
+     * These are the possible values of {@link OmtpConstants#RETURN_CODE}.
+     */
+    public static final String SUCCESS = "0";
+    public static final String SYSTEM_ERROR = "1";
+    public static final String SUBSCRIBER_ERROR = "2";
+    public static final String MAILBOX_UNKNOWN = "3";
+    public static final String VVM_NOT_ACTIVATED = "4";
+    public static final String VVM_NOT_PROVISIONED = "5";
+    public static final String VVM_CLIENT_UKNOWN = "6";
+    public static final String VVM_MAILBOX_NOT_INITIALIZED = "7";
+
+    public static final String[] RETURN_CODE_VALUES = {
+        SUCCESS,
+        SYSTEM_ERROR,
+        SUBSCRIBER_ERROR,
+        MAILBOX_UNKNOWN,
+        VVM_NOT_ACTIVATED,
+        VVM_NOT_PROVISIONED,
+        VVM_CLIENT_UKNOWN,
+        VVM_MAILBOX_NOT_INITIALIZED,
+    };
+
+    /**
+     * A map of all the field keys to the possible values they can have.
+     */
+    public static final Map<String, String[]> possibleValuesMap = new HashMap<String, String[]>() {{
+        put(SYNC_TRIGGER_EVENT, SYNC_TRIGGER_EVENT_VALUES);
+        put(CONTENT_TYPE, CONTENT_TYPE_VALUES);
+        put(PROVISIONING_STATUS, PROVISIONING_STATUS_VALUES);
+        put(RETURN_CODE, RETURN_CODE_VALUES);
+    }};
+
+}
diff --git a/src/com/android/phone/vvm/omtp/OmtpVvmSyncAccountManager.java b/src/com/android/phone/vvm/omtp/OmtpVvmSyncAccountManager.java
new file mode 100644
index 0000000..de39745
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/OmtpVvmSyncAccountManager.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2015 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.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.util.Log;
+
+/**
+ * A singleton class designed to assist in OMTP visual voicemail sync behavior.
+ */
+public class OmtpVvmSyncAccountManager {
+    public static final String TAG = "OmtpVvmSyncAccountManager";
+    // Constants
+    // The authority for the sync adapter's content provider
+    public static final String AUTHORITY = "com.android.voicemail";
+    // An account type, in the form of a domain name
+    public static final String ACCOUNT_TYPE = "com.android.phone.vvm.omtp";
+
+    private static OmtpVvmSyncAccountManager sInstance = new OmtpVvmSyncAccountManager();
+
+    private AccountManager mAccountManager;
+
+    /**
+     * Private constructor. Instance should only be acquired through getInstance().
+     */
+    private OmtpVvmSyncAccountManager() {}
+
+    public static OmtpVvmSyncAccountManager getInstance(Context context) {
+        sInstance.setAccountManager(context);
+        return sInstance;
+    }
+
+    /**
+     * Set the account manager so it does not need to be retrieved every time.
+     * @param context The context to get the account manager for.
+     */
+    private void setAccountManager(Context context) {
+        if (mAccountManager == null) {
+            mAccountManager = AccountManager.get(context);
+        }
+    }
+
+    /**
+     * Register a sync account. There should be a one to one mapping of sync account to voicemail
+     * source. These sync accounts primarily service the purpose of keeping track of how many OMTP
+     * voicemail sources are active and which phone accounts they correspond to.
+     *
+     * @param account The account to register
+     */
+    public void createSyncAccount(Account account) {
+        // Add the account and account type, no password or user data
+        if (mAccountManager.addAccountExplicitly(account, null, null)) {
+             ContentResolver.setIsSyncable(account, AUTHORITY, 1);
+             ContentResolver.setSyncAutomatically(account, AUTHORITY, true);
+        } else {
+            Log.w(TAG, "Attempted to re-register existing account.");
+        }
+    }
+
+    /**
+     * Check if a certain account is registered.
+     *
+     * @param account The account to look for.
+     * @return {@code true} if the account is in the list of registered OMTP voicemail sync
+     * accounts. {@code false} otherwise.
+     */
+    public boolean isAccountRegistered(Account account) {
+        Account[] accounts = mAccountManager.getAccountsByType(ACCOUNT_TYPE);
+        for (int i = 0; i < accounts.length; i++) {
+            if (account.equals(accounts[i])) {
+                return true;
+            }
+        }
+        return false;
+    }
+}
diff --git a/src/com/android/phone/vvm/omtp/OmtpVvmSyncService.java b/src/com/android/phone/vvm/omtp/OmtpVvmSyncService.java
new file mode 100644
index 0000000..811e7e6
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/OmtpVvmSyncService.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 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
+ */
+
+/**
+ * A {@link Service} which runs the internal implementation of {@link AbstractThreadedSyncAdapter},
+ * syncing voicemails to and from a visual voicemail server.
+ */
+
+package com.android.phone.vvm.omtp;
+
+import android.accounts.Account;
+import android.app.Service;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentProviderClient;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SyncResult;
+import android.os.Bundle;
+import android.os.IBinder;
+
+/**
+ * A service to run the VvmSyncAdapter.
+ */
+public class OmtpVvmSyncService extends Service {
+    // Storage for an instance of the sync adapter
+    private static OmtpVvmSyncAdapter sSyncAdapter = null;
+    // Object to use as a thread-safe lock
+    private static final Object sSyncAdapterLock = new Object();
+
+    @Override
+    public void onCreate() {
+        synchronized (sSyncAdapterLock) {
+            if (sSyncAdapter == null) {
+                sSyncAdapter = new OmtpVvmSyncAdapter(getApplicationContext(), true);
+            }
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return sSyncAdapter.getSyncAdapterBinder();
+    }
+
+    public class OmtpVvmSyncAdapter extends AbstractThreadedSyncAdapter {
+        public OmtpVvmSyncAdapter(Context context, boolean autoInitialize) {
+            super(context, autoInitialize);
+        }
+
+        @Override
+        public void onPerformSync(Account account, Bundle extras, String authority,
+                ContentProviderClient provider, SyncResult syncResult) {
+            // TODO: Write code necessary for syncing.
+        }
+    }
+}
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
new file mode 100644
index 0000000..932fb1a
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2015 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.sms;
+
+import android.accounts.Account;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.Telephony;
+import android.provider.VoicemailContract;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.SmsMessage;
+import android.util.Log;
+
+import com.android.internal.telephony.PhoneConstants;
+import com.android.phone.PhoneUtils;
+import com.android.phone.vvm.omtp.OmtpConstants;
+import com.android.phone.vvm.omtp.OmtpVvmSyncAccountManager;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Receive SMS messages and send for processing by the OMTP visual voicemail source.
+ */
+public class OmtpMessageReceiver extends BroadcastReceiver {
+    private static final String TAG = "OmtpMessageReceiver";
+
+    private Context mContext;
+    private PhoneAccountHandle mPhoneAccount;
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        mContext = context;
+        mPhoneAccount = PhoneUtils.makePstnPhoneAccountHandle(
+                intent.getExtras().getInt(PhoneConstants.PHONE_KEY));
+
+        SmsMessage[] messages = Telephony.Sms.Intents.getMessagesFromIntent(intent);
+        StringBuilder userData = new StringBuilder();
+        StringBuilder messageBody = new StringBuilder();
+
+        for (int i = 0; i < messages.length; i++) {
+            messageBody.append(messages[i].getMessageBody());
+            userData.append(extractUserData(messages[i]));
+        }
+
+        WrappedMessageData messageData = OmtpSmsParser.parse(messageBody.toString());
+        if (messageData != null) {
+            if (messageData.getPrefix() == OmtpConstants.SYNC_SMS_PREFIX) {
+                SyncMessage message = new SyncMessage(messageData);
+                //TODO: handle message
+            } else if (messageData.getPrefix() == OmtpConstants.STATUS_SMS_PREFIX) {
+                StatusMessage message = new StatusMessage(messageData);
+                handleStatusMessage(message);
+            } else {
+                Log.e(TAG, "This should never have happened");
+            }
+        }
+        // Let this fall through: this is not a message we're interested in.
+    }
+
+    private String extractUserData(SmsMessage sms) {
+        try {
+            // OMTP spec does not tell about the encoding. We assume ASCII.
+            // UTF-8 sounds safer as it can handle ascii as well as other charsets.
+            return new String(sms.getUserData(), "UTF-8");
+        } catch (UnsupportedEncodingException e) {
+            throw new IllegalStateException("This should have never happened", e);
+        }
+    }
+
+    private void handleStatusMessage(StatusMessage message) {
+        OmtpVvmSyncAccountManager vvmSyncManager = OmtpVvmSyncAccountManager.getInstance(mContext);
+        Account account = new Account(mPhoneAccount.getId(),
+                OmtpVvmSyncAccountManager.ACCOUNT_TYPE);
+
+        if (!vvmSyncManager.isAccountRegistered(account)) {
+            // If the account has not been previously registered, it means that this STATUS sms
+            // is a result of the ACTIVATE sms, so register the voicemail source.
+            vvmSyncManager.createSyncAccount(account);
+            VoicemailContract.Status.setStatus(mContext, mPhoneAccount,
+                    VoicemailContract.Status.CONFIGURATION_STATE_OK,
+                    VoicemailContract.Status.DATA_CHANNEL_STATE_OK,
+                    VoicemailContract.Status.NOTIFICATION_CHANNEL_STATE_OK);
+        }
+
+        //TODO: figure out how to pass IMAP credentials to sync adapter
+
+        ContentResolver.requestSync(account, VoicemailContract.AUTHORITY, new Bundle());
+    }
+}
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpMessageSender.java b/src/com/android/phone/vvm/omtp/sms/OmtpMessageSender.java
new file mode 100644
index 0000000..f7cfb86
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpMessageSender.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2015 Google Inc. All Rights Reserved.
+ *
+ * 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.sms;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.telephony.SmsManager;
+
+/**
+ * Interface to send client originated OMTP messages to the OMTP server.
+ * <p>
+ * The interface uses {@link PendingIntent} instead of a call back to notify when the message is
+ * sent. This is primarily to keep the implementation simple and reuse what the underlying
+ * {@link SmsManager} interface provides.
+ */
+public interface OmtpMessageSender {
+    /**
+     * Sends a request to the VVM server to activate VVM for the current subscriber.
+     *
+     * @param sentIntent If not NULL this PendingIntent is broadcast when the message is
+     *            successfully sent, or failed.
+     */
+    public void requestVvmActivation(@Nullable PendingIntent sentIntent);
+
+    /**
+     * Sends a request to the VVM server to deactivate VVM for the current subscriber.
+     *
+     * @param sentIntent If not NULL this PendingIntent is broadcast when the message is
+     *            successfully sent, or failed.
+     */
+    public void requestVvmDeactivation(@Nullable PendingIntent sentIntent);
+
+    /**
+     * Send a request to the VVM server to get account status of the current subscriber.
+     *
+     * @param sentIntent If not NULL this PendingIntent is broadcast when the message is
+     *            successfully sent, or failed.
+     */
+    public void requestVvmStatus(@Nullable PendingIntent sentIntent);
+}
\ No newline at end of file
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpMessageSenderImpl.java b/src/com/android/phone/vvm/omtp/sms/OmtpMessageSenderImpl.java
new file mode 100644
index 0000000..15e8e3a
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpMessageSenderImpl.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2015 Google Inc. All Rights Reserved.
+ *
+ * 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.sms;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+import android.telephony.SmsManager;
+import android.text.TextUtils;
+
+import com.android.phone.vvm.omtp.OmtpConstants;
+import com.android.services.telephony.Log;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Implementation of {@link OmtpMessageSender} interface.
+ * <p>
+ * Provides simple APIs to send different types of mobile originated OMTP SMS to the VVM server.
+ */
+public class OmtpMessageSenderImpl implements OmtpMessageSender {
+    private static final String TAG = "OmtpMessageSender";
+    private final SmsManager mSmsManager;
+    private final short mApplicationPort;
+    private final String mDestinationNumber;
+    private final String mClientType;
+    private final String mProtocolVersion;
+    private final String mClientPrefix;
+
+    /**
+     * Creates a new instance of OmtpMessageSenderImpl.
+     *
+     * @param smsManager SMS sending library. There is a different SmsManager for each SIM.
+     * @param applicationPort If set to a value > 0 then a binary sms is sent to this port number.
+     *            Otherwise, a standard text SMS is sent.
+     * @param defaultDestinationNumber Destination number to be used.
+     * @param clientType The "ct" field to be set in the MO message. This is the value used by the
+     *            VVM server to identify the client. Certain VVM servers require a specific agreed
+     *            value for this field.
+     * @param protocolVersion OMTP protocol version.
+     * @param clientPrefix The client prefix requested to be used by the server in its MT messages.
+     */
+    public OmtpMessageSenderImpl(SmsManager smsManager, short applicationPort,
+            String defaultDestinationNumber, String clientType, String protocolVersion,
+            String clientPrefix) {
+        mSmsManager = smsManager;
+        mApplicationPort = applicationPort;
+        mDestinationNumber = defaultDestinationNumber;
+        mClientType = clientType;
+        mProtocolVersion = protocolVersion;
+        mClientPrefix = clientPrefix;
+    }
+
+
+    // Activate message:
+    // V1.1: Activate:pv=<value>;ct=<value>
+    // V1.2: Activate:pv=<value>;ct=<value>;pt=<value>;<Clientprefix>
+    // V1.3: Activate:pv=<value>;ct=<value>;pt=<value>;<Clientprefix>
+    @Override
+    public void requestVvmActivation(@Nullable PendingIntent sentIntent) {
+        StringBuilder sb = new StringBuilder().append(OmtpConstants.ACTIVATE_REQUEST);
+
+        appendProtocolVersionAndClientType(sb);
+        if (TextUtils.equals(mProtocolVersion, OmtpConstants.PROTOCOL_VERSION1_2) ||
+                TextUtils.equals(mProtocolVersion, OmtpConstants.PROTOCOL_VERSION1_3)) {
+            appendApplicationPort(sb);
+            appendClientPrefix(sb);
+        }
+
+        sendSms(sb.toString(), sentIntent);
+    }
+
+    // Deactivate message:
+    // V1.1: Deactivate:pv=<value>;ct=<string>
+    // V1.2: Deactivate:pv=<value>;ct=<string>
+    // V1.3: Deactivate:pv=<value>;ct=<string>
+    @Override
+    public void requestVvmDeactivation(@Nullable PendingIntent sentIntent) {
+        StringBuilder sb = new StringBuilder().append(OmtpConstants.DEACTIVATE_REQUEST);
+        appendProtocolVersionAndClientType(sb);
+
+        sendSms(sb.toString(), sentIntent);
+    }
+
+    // Status message:
+    // V1.1: STATUS
+    // V1.2: STATUS
+    // V1.3: STATUS:pv=<value>;ct=<value>;pt=<value>;<Clientprefix>
+    @Override
+    public void requestVvmStatus(@Nullable PendingIntent sentIntent) {
+        StringBuilder sb = new StringBuilder().append(OmtpConstants.STATUS_REQUEST);
+
+        if (TextUtils.equals(mProtocolVersion, OmtpConstants.PROTOCOL_VERSION1_3)) {
+            appendProtocolVersionAndClientType(sb);
+            appendApplicationPort(sb);
+            appendClientPrefix(sb);
+        }
+
+        sendSms(sb.toString(), sentIntent);
+    }
+
+    private void sendSms(String text, PendingIntent sentIntent) {
+        // If application port is set to 0 then send simple text message, else send data message.
+        if (mApplicationPort == 0) {
+            Log.v(TAG, String.format("Sending TEXT sms '%s' to %s", text, mDestinationNumber));
+            mSmsManager.sendTextMessage(mDestinationNumber, null, text, sentIntent, null);
+        } else {
+            byte[] data;
+            try {
+                data = text.getBytes("UTF-8");
+            } catch (UnsupportedEncodingException e) {
+                throw new IllegalStateException("Failed to encode: " + text);
+            }
+            Log.v(TAG, String.format("Sending BINARY sms '%s' to %s:%d", text, mDestinationNumber,
+                    mApplicationPort));
+            mSmsManager.sendDataMessage(mDestinationNumber, null, mApplicationPort, data,
+                    sentIntent, null);
+        }
+    }
+
+    private void appendProtocolVersionAndClientType(StringBuilder sb) {
+        sb.append(OmtpConstants.SMS_PREFIX_SEPARATOR);
+        appendField(sb, OmtpConstants.PROTOCOL_VERSION, mProtocolVersion);
+        sb.append(OmtpConstants.SMS_FIELD_SEPARATOR);
+        appendField(sb, OmtpConstants.CLIENT_TYPE, mClientType);
+    }
+
+    private void appendApplicationPort(StringBuilder sb) {
+        sb.append(OmtpConstants.SMS_FIELD_SEPARATOR);
+        appendField(sb, OmtpConstants.APPLICATION_PORT, mApplicationPort);
+    }
+
+    private void appendClientPrefix(StringBuilder sb) {
+        sb.append(OmtpConstants.SMS_FIELD_SEPARATOR);
+        sb.append(mClientPrefix);
+    }
+
+    private void appendField(StringBuilder sb, String field, Object value) {
+        sb.append(field).append(OmtpConstants.SMS_KEY_VALUE_SEPARATOR).append(value);
+    }
+}
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpSmsParser.java b/src/com/android/phone/vvm/omtp/sms/OmtpSmsParser.java
new file mode 100644
index 0000000..54a2a02
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpSmsParser.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 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.sms;
+
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.phone.vvm.omtp.OmtpConstants;
+
+import java.util.Map;
+
+/**
+ * OMTP SMS parser interface, for parsing SYNC and STATUS SMS sent by OMTP visual voicemail server.
+ */
+public class OmtpSmsParser {
+    private static String TAG = "OmtpSmsParser";
+    /**
+     * Parses the supplied SMS body and returns back a structured OMTP message.
+     * Returns null if unable to parse the SMS body.
+     */
+    public static WrappedMessageData parse(String smsBody) {
+        if (smsBody == null) {
+            return null;
+        }
+
+        WrappedMessageData messageData = null;
+        if (smsBody.startsWith(OmtpConstants.SYNC_SMS_PREFIX)) {
+            messageData = new WrappedMessageData(OmtpConstants.SYNC_SMS_PREFIX,
+                    parseSmsBody(smsBody.substring(OmtpConstants.SYNC_SMS_PREFIX.length())));
+            // Check for a mandatory field.
+            String triggerEvent = messageData.extractString(OmtpConstants.SYNC_TRIGGER_EVENT);
+            if (triggerEvent == null) {
+                Log.e(TAG, "Missing mandatory field: " + OmtpConstants.SYNC_TRIGGER_EVENT);
+                return null;
+            }
+        } else if (smsBody.startsWith(OmtpConstants.STATUS_SMS_PREFIX)) {
+            messageData = new WrappedMessageData(OmtpConstants.STATUS_SMS_PREFIX,
+                    parseSmsBody(smsBody.substring(OmtpConstants.STATUS_SMS_PREFIX.length())));
+        }
+
+        return messageData;
+    }
+
+    /**
+     * Converts a String of key/value pairs into a Map object. The WrappedMessageData object
+     * contains helper functions to retrieve the values.
+     *
+     * e.g. "//VVM:STATUS:st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1"
+     * => "WrappedMessageData [mFields={st=R, ipt=1, srv=1, dn=1, u=eg@example.com, pw=1, rc=0}]"
+     *
+     * @param message The sms string with the prefix removed.
+     * @return A WrappedMessageData object containing the map.
+     */
+    private static Map<String, String> parseSmsBody(String message) {
+        Map<String, String> keyValues = new ArrayMap<String, String>();
+        String[] entries = message.split(OmtpConstants.SMS_FIELD_SEPARATOR);
+        for (String entry : entries) {
+            String[] keyValue = entry.split(OmtpConstants.SMS_KEY_VALUE_SEPARATOR);
+            if (keyValue.length != 2) {
+                continue;
+            }
+            keyValues.put(keyValue[0].trim(), keyValue[1].trim());
+        }
+
+        return keyValues;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/phone/vvm/omtp/sms/StatusMessage.java b/src/com/android/phone/vvm/omtp/sms/StatusMessage.java
new file mode 100644
index 0000000..7e4faac
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sms/StatusMessage.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2015 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.sms;
+
+import android.telecom.Log;
+
+import com.android.phone.vvm.omtp.OmtpConstants;
+
+/**
+ * Structured data representation of OMTP STATUS message.
+ *
+ * The getters will return null if the field was not set in the message body or it could not be
+ * parsed.
+ */
+public class StatusMessage {
+    // NOTE: Following Status SMS fields are not yet parsed, as they do not seem
+    // to be useful for initial omtp source implementation.
+    // lang, g_len, vs_len, pw_len, pm, gm, vtc, vt
+
+    private final String mProvisioningStatus;
+    private final String mStatusReturnCode;
+    private final String mSubscriptionUrl;
+    private final String mServerAddress;
+    private final String mTuiAccessNumber;
+    private final String mClientSmsDestinationNumber;
+    private final String mImapPort;
+    private final String mImapUserName;
+    private final String mImapPassword;
+    private final String mSmtpPort;
+    private final String mSmtpUserName;
+    private final String mSmtpPassword;
+
+    @Override
+    public String toString() {
+        return "StatusMessage [mProvisioningStatus=" + mProvisioningStatus
+                + ", mStatusReturnCode=" + mStatusReturnCode
+                + ", mSubscriptionUrl=" + mSubscriptionUrl
+                + ", mServerAddress=" + mServerAddress
+                + ", mTuiAccessNumber=" + mTuiAccessNumber
+                + ", mClientSmsDestinationNumber=" + mClientSmsDestinationNumber
+                + ", mImapPort=" + mImapPort
+                + ", mImapUserName=" + mImapUserName
+                + ", mImapPassword=" + Log.pii(mImapPassword)
+                + ", mSmtpPort=" + mSmtpPort
+                + ", mSmtpUserName=" + mSmtpUserName
+                + ", mSmtpPassword=" + Log.pii(mSmtpPassword) + "]";
+    }
+
+    public StatusMessage(WrappedMessageData wrappedData) {
+        mProvisioningStatus = wrappedData.extractString(OmtpConstants.PROVISIONING_STATUS);
+        mStatusReturnCode = wrappedData.extractString(OmtpConstants.RETURN_CODE);
+        mSubscriptionUrl = wrappedData.extractString(OmtpConstants.SUBSCRIPTION_URL);
+        mServerAddress = wrappedData.extractString(OmtpConstants.SERVER_ADDRESS);
+        mTuiAccessNumber = wrappedData.extractString(OmtpConstants.TUI_ACCESS_NUMBER);
+        mClientSmsDestinationNumber = wrappedData.extractString(
+                OmtpConstants.CLIENT_SMS_DESTINATION_NUMBER);
+        mImapPort = wrappedData.extractString(OmtpConstants.IMAP_PORT);
+        mImapUserName = wrappedData.extractString(OmtpConstants.IMAP_USER_NAME);
+        mImapPassword = wrappedData.extractString(OmtpConstants.IMAP_PASSWORD);
+        mSmtpPort = wrappedData.extractString(OmtpConstants.SMTP_PORT);
+        mSmtpUserName = wrappedData.extractString(OmtpConstants.SMTP_USER_NAME);
+        mSmtpPassword = wrappedData.extractString(OmtpConstants.SMTP_PASSWORD);
+    }
+
+    /**
+     * @return the subscriber's VVM provisioning status.
+     */
+    public String getProvisioningStatus() {
+        return mProvisioningStatus;
+    }
+
+    /**
+     * @return the return-code of the status SMS.
+     */
+    public String getReturnCode() {
+        return mStatusReturnCode;
+    }
+
+    /**
+     * @return the URL of the voicemail server. This is the URL to send the users to for subscribing
+     * to the visual voicemail service.
+     */
+    public String getSubscriptionUrl() {
+        return mSubscriptionUrl;
+    }
+
+    /**
+     * @return the voicemail server address. Either server IP address or fully qualified domain
+     * name.
+     */
+    public String getServerAddress() {
+        return mServerAddress;
+    }
+
+    /**
+     * @return the Telephony User Interface number to call to access voicemails directly from the
+     * IVR.
+     */
+    public String getTuiAccessNumber() {
+        return mTuiAccessNumber;
+    }
+
+    /**
+     * @return the number to which client originated SMSes should be sent to.
+     */
+    public String getClientSmsDestinationNumber() {
+        return mClientSmsDestinationNumber;
+    }
+
+    /**
+     * @return the IMAP server port to talk to.
+     */
+    public String getImapPort() {
+        return mImapPort;
+    }
+
+    /**
+     * @return the IMAP user name to be used for authentication.
+     */
+    public String getImapUserName() {
+        return mImapUserName;
+    }
+
+    /**
+     * @return the IMAP password to be used for authentication.
+     */
+    public String getImapPassword() {
+        return mImapPassword;
+    }
+
+    /**
+     * @return the SMTP server port to talk to.
+     */
+    public String getSmtpPort() {
+        return mSmtpPort;
+    }
+
+    /**
+     * @return the SMTP user name to be used for SMTP authentication.
+     */
+    public String getSmtpUserName() {
+        return mSmtpUserName;
+    }
+
+    /**
+     * @return the SMTP password to be used for SMTP authentication.
+     */
+    public String getSmtpPassword() {
+        return mSmtpPassword;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/phone/vvm/omtp/sms/SyncMessage.java b/src/com/android/phone/vvm/omtp/sms/SyncMessage.java
new file mode 100644
index 0000000..9a78a6d
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sms/SyncMessage.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2015 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.sms;
+
+import com.android.phone.vvm.omtp.OmtpConstants;
+
+/**
+ * Structured data representation of an OMTP SYNC message.
+ *
+ * Getters will return null if the field was not set in the message body or it could not be parsed.
+ */
+public class SyncMessage {
+    // Sync event that triggered this message.
+    private final String mSyncTriggerEvent;
+    // Total number of new messages on the server.
+    private final Integer mNewMessageCount;
+    // UID of the new message.
+    private final String mMessageId;
+    // Length of the message.
+    private final Integer mMessageLength;
+    // Content type (voice, video, fax...) of the new message.
+    private final String mContentType;
+    // Sender of the new message.
+    private final String mSender;
+    // Timestamp (in millis) of the new message.
+    private final Long mMsgTimeMillis;
+
+    @Override
+    public String toString() {
+        return "SyncMessage [mSyncTriggerEvent=" + mSyncTriggerEvent
+                + ", mNewMessageCount=" + mNewMessageCount
+                + ", mMessageId=" + mMessageId
+                + ", mMessageLength=" + mMessageLength
+                + ", mContentType=" + mContentType
+                + ", mSender=" + mSender
+                + ", mMsgTimeMillis=" + mMsgTimeMillis + "]";
+    }
+
+    public SyncMessage(WrappedMessageData wrappedData) {
+        mSyncTriggerEvent = wrappedData.extractString(OmtpConstants.SYNC_TRIGGER_EVENT);
+        mMessageId = wrappedData.extractString(OmtpConstants.MESSAGE_UID);
+        mMessageLength = wrappedData.extractInteger(OmtpConstants.MESSAGE_LENGTH);
+        mContentType = wrappedData.extractString(OmtpConstants.CONTENT_TYPE);
+        mSender = wrappedData.extractString(OmtpConstants.SENDER);
+        mNewMessageCount = wrappedData.extractInteger(OmtpConstants.NUM_MESSAGE_COUNT);
+        mMsgTimeMillis = wrappedData.extractTime(OmtpConstants.TIME);
+    }
+
+    /**
+     * @return the event that triggered the sync message. This is a mandatory field and must always
+     * be set.
+     */
+    public String getSyncTriggerEvent() {
+        return mSyncTriggerEvent;
+    }
+
+    /**
+     * @return the number of new messages stored on the voicemail server.
+     */
+    public int getNewMessageCount() {
+        return mNewMessageCount;
+    }
+
+    /**
+     * @return the message ID of the new message.
+     * <p>
+     * Expected to be set only for
+     * {@link com.android.phone.vvm.omtp.OmtpConstants#NEW_MESSAGE}
+     */
+    public String getId() {
+        return mMessageId;
+    }
+
+    /**
+     * @return the content type of the new message.
+     * <p>
+     * Expected to be set only for
+     * {@link com.android.phone.vvm.omtp.OmtpConstants#NEW_MESSAGE}
+     */
+    public String getContentType() {
+        return mContentType;
+    }
+
+    /**
+     * @return the message length of the new message.
+     * <p>
+     * Expected to be set only for
+     * {@link com.android.phone.vvm.omtp.OmtpConstants#NEW_MESSAGE}
+     */
+    public int getLength() {
+        return mMessageLength;
+    }
+
+    /**
+     * @return the sender's phone number of the new message specified as MSISDN.
+     * <p>
+     * Expected to be set only for
+     * {@link com.android.phone.vvm.omtp.OmtpConstants#NEW_MESSAGE}
+     */
+    public String getSender() {
+        return mSender;
+    }
+
+    /**
+     * @return the timestamp as milliseconds for the new message.
+     * <p>
+     * Expected to be set only for
+     * {@link com.android.phone.vvm.omtp.OmtpConstants#NEW_MESSAGE}
+     */
+    public long getTimestampMillis() {
+        return mMsgTimeMillis;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/phone/vvm/omtp/sms/WrappedMessageData.java b/src/com/android/phone/vvm/omtp/sms/WrappedMessageData.java
new file mode 100644
index 0000000..109dfb2
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sms/WrappedMessageData.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2015 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.sms;
+
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.phone.vvm.omtp.OmtpConstants;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Class wrapping the raw OMTP message data, internally represented as as map of all key-value pairs
+ * found in the SMS body.
+ * <p>
+ * Provides convenience methods to extract parse fields of different types.
+ * <p>
+ * All the methods return null if either the field was not present or it could not be parsed.
+ */
+public class WrappedMessageData {
+    private final String TAG = "WrappedMessageData";
+    private final String mPrefix;
+    private final Map<String, String> mFields;
+
+    @Override
+    public String toString() {
+        return "WrappedMessageData [mFields=" + mFields + "]";
+    }
+
+    WrappedMessageData(String prefix, Map<String, String> keyValues) {
+        mPrefix = prefix;
+        mFields = new ArrayMap<String, String>();
+        mFields.putAll(keyValues);
+    }
+
+    /**
+     * @return The String prefix of the message, designating whether this is the message data of a
+     * STATUS or SYNC sms.
+     */
+    String getPrefix() {
+        return mPrefix;
+    }
+
+    /**
+     * Extracts the requested field from underlying data and returns the String value as is.
+     *
+     * @param field The requested field.
+     * @return the parsed string value, or null if the field was not present or not valid.
+     */
+    String extractString(final String field) {
+        String value = mFields.get(field);
+        String[] possibleValues = OmtpConstants.possibleValuesMap.get(field);
+        if (possibleValues == null) {
+            return value;
+        }
+        for (int i = 0; i < possibleValues.length; i++) {
+            if (TextUtils.equals(value, possibleValues[i])) {
+                return value;
+            }
+        }
+        Log.e(TAG, "extractString - value \"" + value +
+                "\" of field \"" + field + "\" is not allowed.");
+        return null;
+    }
+
+    /**
+     * Extracts the requested field from underlying data and parses it as an {@link Integer}.
+     *
+     * @param field The requested field.
+     * @return the parsed integer value, or null if the field was not present.
+     */
+    Integer extractInteger(final String field) {
+        String value = mFields.get(field);
+        if (value == null) {
+            return null;
+        }
+
+        try {
+            return Integer.decode(value);
+        } catch (NumberFormatException e) {
+            Log.e(TAG, "extractInteger - could not parse integer: " + value);
+            return null;
+        }
+    }
+
+    /**
+     * Extracts the requested field from underlying data and parses it as a date/time represented in
+     * {@link OmtpConstants#DATE_TIME_FORMAT} format.
+     *
+     * @param field The requested field.
+     * @return the parsed string value, or null if the field was not present.
+     */
+    Long extractTime(final String field) {
+        String value = mFields.get(field);
+        if (value == null) {
+            return null;
+        }
+
+        try {
+            return new SimpleDateFormat(
+                    OmtpConstants.DATE_TIME_FORMAT, Locale.US).parse(value).getTime();
+        } catch (ParseException e) {
+            Log.e(TAG, "extractTime - could not parse time: " + value);
+            return null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/services/telephony/CdmaConnection.java b/src/com/android/services/telephony/CdmaConnection.java
index 0baa3cc..3016039 100644
--- a/src/com/android/services/telephony/CdmaConnection.java
+++ b/src/com/android/services/telephony/CdmaConnection.java
@@ -155,13 +155,16 @@
         mIsCallWaiting = originalConnection != null &&
                 originalConnection.getState() == Call.State.WAITING;
 
-        if (state == android.telecom.Connection.STATE_DIALING) {
-            if (isEmergency()) {
-                mEmergencyTonePlayer.start();
+        if (mEmergencyTonePlayer != null) {
+            if (state == android.telecom.Connection.STATE_DIALING) {
+                if (isEmergency()) {
+                    mEmergencyTonePlayer.start();
+                }
+            } else {
+                // No need to check if it is an emergency call, since it is a no-op if it
+                // isn't started.
+                mEmergencyTonePlayer.stop();
             }
-        } else {
-            // No need to check if it is an emergency call, since it is a no-op if it isn't started.
-            mEmergencyTonePlayer.stop();
         }
 
         super.onStateChanged(state);
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index e692a30..9afb4ad 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -21,7 +21,11 @@
 import android.telecom.ConferenceParticipant;
 import android.telecom.Connection;
 import android.telecom.DisconnectCause;
+import android.telecom.Log;
 import android.telecom.PhoneAccountHandle;
+import android.telecom.VideoProfile;
+import android.telecom.Conference.Listener;
+import android.telecom.Connection.VideoProvider;
 
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CallStateException;
@@ -145,6 +149,28 @@
             TelephonyConnection telephonyConnection = (TelephonyConnection) c;
             handleConferenceParticipantsUpdate(telephonyConnection, participants);
         }
+
+        @Override
+        public void onVideoStateChanged(android.telecom.Connection c, int videoState) {
+            Log.d(this, "onVideoStateChanged video state %d", videoState);
+            setVideoState(c, videoState);
+        }
+
+        @Override
+        public void onVideoProviderChanged(android.telecom.Connection c,
+                Connection.VideoProvider videoProvider) {
+            Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c,
+                    videoProvider);
+            setVideoProvider(c, videoProvider);
+        }
+
+        @Override
+        public void onConnectionCapabilitiesChanged(Connection c, int connectionCapabilities) {
+            Log.d(this, "onCallCapabilitiesChanged: Connection: %s, callCapabilities: %s", c,
+                    connectionCapabilities);
+            int capabilites = ImsConference.this.getCapabilities();
+            setCapabilities(applyVideoCapabilities(capabilites, connectionCapabilities));
+        }
     };
 
     /**
@@ -191,11 +217,39 @@
             Log.v(this, "set phacc to " + mPhoneAccount);
         }
 
-        setConnectionCapabilities(
-                Connection.CAPABILITY_SUPPORT_HOLD |
-                Connection.CAPABILITY_HOLD |
-                Connection.CAPABILITY_MUTE
-        );
+        int capabilities = Connection.CAPABILITY_SUPPORT_HOLD | Connection.CAPABILITY_HOLD |
+                Connection.CAPABILITY_MUTE;
+
+        capabilities = applyVideoCapabilities(capabilities, mConferenceHost.getCallCapabilities());
+        setConnectionCapabilities(capabilities);
+
+    }
+
+    private int applyVideoCapabilities(int conferenceCapabilities, int capabilities) {
+        if (can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_LOCAL)) {
+            conferenceCapabilities = applyCapability(conferenceCapabilities,
+                    Connection.CAPABILITY_SUPPORTS_VT_LOCAL);
+        } else {
+            conferenceCapabilities = removeCapability(conferenceCapabilities,
+                    Connection.CAPABILITY_SUPPORTS_VT_LOCAL);
+        }
+
+        if (can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_REMOTE)) {
+            conferenceCapabilities = applyCapability(conferenceCapabilities,
+                    Connection.CAPABILITY_SUPPORTS_VT_REMOTE);
+        } else {
+            conferenceCapabilities = removeCapability(conferenceCapabilities,
+                    Connection.CAPABILITY_SUPPORTS_VT_REMOTE);
+        }
+
+        if (can(capabilities, Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO)) {
+            conferenceCapabilities = applyCapability(conferenceCapabilities,
+                    Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO);
+        } else {
+            conferenceCapabilities = removeCapability(conferenceCapabilities,
+                    Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO);
+        }
+        return conferenceCapabilities;
     }
 
     /**
@@ -209,6 +263,32 @@
     }
 
     /**
+     * Returns VideoProvider of the conference. This can be null.
+     *
+     * @hide
+     */
+    @Override
+    public VideoProvider getVideoProvider() {
+        if (mConferenceHost != null) {
+            return mConferenceHost.getVideoProvider();
+        }
+        return null;
+    }
+
+    /**
+     * Returns video state of conference
+     *
+     * @hide
+     */
+    @Override
+    public int getVideoState() {
+        if (mConferenceHost != null) {
+            return mConferenceHost.getVideoState();
+        }
+        return VideoProfile.VideoState.AUDIO_ONLY;
+    }
+
+    /**
      * Invoked when the Conference and all its {@link Connection}s should be disconnected.
      * <p>
      * Hangs up the call via the conference host connection.  When the host connection has been
@@ -321,6 +401,16 @@
         // No-op
     }
 
+    private int applyCapability(int capabilities, int capability) {
+        int newCapabilities = capabilities | capability;
+        return newCapabilities;
+    }
+
+    private int removeCapability(int capabilities, int capability) {
+        int newCapabilities = capabilities & ~capability;
+        return newCapabilities;
+    }
+
     /**
      * Updates the manage conference capability of the conference.  Where there are one or more
      * conference event package participants, the conference management is permitted.  Where there
@@ -356,6 +446,7 @@
         mConferenceHost = conferenceHost;
         mConferenceHost.addConnectionListener(mConferenceHostListener);
         mConferenceHost.addTelephonyConnectionListener(mTelephonyConnectionListener);
+        setState(mConferenceHost.getState());
     }
 
     /**
diff --git a/src/com/android/services/telephony/PstnPhoneCapabilitiesNotifier.java b/src/com/android/services/telephony/PstnPhoneCapabilitiesNotifier.java
new file mode 100644
index 0000000..48568d3
--- /dev/null
+++ b/src/com/android/services/telephony/PstnPhoneCapabilitiesNotifier.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2015 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.services.telephony;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneProxy;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.util.Preconditions;
+import com.android.phone.PhoneUtils;
+
+/**
+ * Listens to phone's capabilities changed event and notifies Telecomm. One instance of these exists
+ * for each of the telephony-based call services.
+ */
+final class PstnPhoneCapabilitiesNotifier {
+    private static final int EVENT_VIDEO_CAPABILITIES_CHANGED = 1;
+
+    private final PhoneProxy mPhoneProxy;
+    private Phone mPhoneBase;
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case EVENT_VIDEO_CAPABILITIES_CHANGED:
+                    handleVideoCapabilitesChanged((AsyncResult) msg.obj);
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+
+    private final BroadcastReceiver mRatReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED.equals(action)) {
+                String newPhone = intent.getStringExtra(PhoneConstants.PHONE_NAME_KEY);
+                Log.d(this, "Radio technology switched. Now %s is active.", newPhone);
+
+                registerForNotifications();
+            }
+        }
+    };
+
+    /*package*/
+    PstnPhoneCapabilitiesNotifier(PhoneProxy phoneProxy) {
+        Preconditions.checkNotNull(phoneProxy);
+
+        mPhoneProxy = phoneProxy;
+
+        registerForNotifications();
+
+        IntentFilter intentFilter =
+                new IntentFilter(TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED);
+        mPhoneProxy.getContext().registerReceiver(mRatReceiver, intentFilter);
+    }
+
+    /*package*/
+    void teardown() {
+        unregisterForNotifications();
+        mPhoneProxy.getContext().unregisterReceiver(mRatReceiver);
+    }
+
+    private void registerForNotifications() {
+        Phone newPhone = mPhoneProxy.getActivePhone();
+        if (newPhone != mPhoneBase) {
+            unregisterForNotifications();
+
+            if (newPhone != null) {
+                Log.d(this, "Registering: " + newPhone);
+                mPhoneBase = newPhone;
+                mPhoneBase.registerForVideoCapabilityChanged(
+                        mHandler, EVENT_VIDEO_CAPABILITIES_CHANGED, null);
+            }
+        }
+    }
+
+    private void unregisterForNotifications() {
+        if (mPhoneBase != null) {
+            Log.d(this, "Unregistering: " + mPhoneBase);
+            mPhoneBase.unregisterForVideoCapabilityChanged(mHandler);
+        }
+    }
+
+    private void handleVideoCapabilitesChanged(AsyncResult ar) {
+        try {
+            boolean isVideoCapable = (Boolean) ar.result;
+            Log.d(this, "handleVideoCapabilitesChanged. Video capability - " + isVideoCapable);
+            PhoneAccountHandle accountHandle =
+                    PhoneUtils.makePstnPhoneAccountHandle(mPhoneProxy);
+            TelecomManager telecomMgr = TelecomManager.from(mPhoneProxy.getContext());
+            PhoneAccount oldPhoneAccount = telecomMgr.getPhoneAccount(accountHandle);
+            PhoneAccount.Builder builder = new PhoneAccount.Builder(oldPhoneAccount);
+
+            int capabilites = newCapabilities(oldPhoneAccount.getCapabilities(),
+                    PhoneAccount.CAPABILITY_VIDEO_CALLING, isVideoCapable);
+            builder.setCapabilities(capabilites);
+            telecomMgr.registerPhoneAccount(builder.build());
+        } catch (Exception e) {
+            Log.d(this, "handleVideoCapabilitesChanged. Exception=" + e);
+        }
+    }
+
+    private int newCapabilities(int capabilities, int capability, boolean set) {
+        if (set) {
+            capabilities |= capability;
+        } else {
+            capabilities &= ~capability;
+        }
+        return capabilities;
+    }
+}
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index 1cb6442..866118c 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -57,6 +57,7 @@
         private final Phone mPhone;
         private final PhoneAccount mAccount;
         private final PstnIncomingCallNotifier mIncomingCallNotifier;
+        private final PstnPhoneCapabilitiesNotifier mPhoneCapabilitiesNotifier;
 
         AccountEntry(Phone phone, boolean isEmergency, boolean isDummy) {
             mPhone = phone;
@@ -64,10 +65,12 @@
             Log.d(this, "Registered phoneAccount: %s with handle: %s",
                     mAccount, mAccount.getAccountHandle());
             mIncomingCallNotifier = new PstnIncomingCallNotifier((PhoneProxy) mPhone);
+            mPhoneCapabilitiesNotifier = new PstnPhoneCapabilitiesNotifier((PhoneProxy) mPhone);
         }
 
         void teardown() {
             mIncomingCallNotifier.teardown();
+            mPhoneCapabilitiesNotifier.teardown();
         }
 
         /**
@@ -146,6 +149,10 @@
                     PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS |
                     PhoneAccount.CAPABILITY_MULTI_USER;
 
+            if (mPhone.isVideoEnabled()) {
+                capabilities |= PhoneAccount.CAPABILITY_VIDEO_CALLING;
+            }
+
             if (iconBitmap == null) {
                 iconBitmap = BitmapFactory.decodeResource(
                         mContext.getResources(),
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index ac384fd..f0b6049 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -192,6 +192,17 @@
         public void onConferenceParticipantsChanged(List<ConferenceParticipant> participants) {
             updateConferenceParticipants(participants);
         }
+
+        /**
+         * Used by the {@link com.android.internal.telephony.Connection} to report a change in the
+         * substate of the current call
+         *
+         * @param callSubstate The call substate.
+         */
+        @Override
+        public void onCallSubstateChanged(int callSubstate) {
+            setCallSubstate(callSubstate);
+        }
     };
 
     private com.android.internal.telephony.Connection mOriginalConnection;
@@ -450,6 +461,13 @@
                 callCapabilities |= CAPABILITY_HOLD;
             }
         }
+
+        // If the phone is in ECM mode, mark the call to indicate that the callback number should be
+        // shown.
+        Phone phone = getPhone();
+        if (phone != null && phone.isInEcm()) {
+            callCapabilities |= CAPABILITY_SHOW_CALLBACK_NUMBER;
+        }
         return callCapabilities;
     }
 
@@ -512,11 +530,13 @@
 
         // Set video state and capabilities
         setVideoState(mOriginalConnection.getVideoState());
+        updateState();
         setLocalVideoCapable(mOriginalConnection.isLocalVideoCapable());
         setRemoteVideoCapable(mOriginalConnection.isRemoteVideoCapable());
         setWifi(mOriginalConnection.isWifi());
         setVideoProvider(mOriginalConnection.getVideoProvider());
         setAudioQuality(mOriginalConnection.getAudioQuality());
+        setCallSubstate(mOriginalConnection.getCallSubstate());
 
         if (isImsConnection()) {
             mWasImsConnection = true;
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 1fb70c0..39dbfc1 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -26,6 +26,7 @@
 import android.telecom.PhoneAccountHandle;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.ServiceState;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 
@@ -38,6 +39,7 @@
 import com.android.internal.telephony.SubscriptionController;
 import com.android.internal.telephony.cdma.CDMAPhone;
 import com.android.phone.MMIDialogActivity;
+import com.android.phone.PhoneUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -396,22 +398,14 @@
     }
 
     private Phone getPhoneForAccount(PhoneAccountHandle accountHandle, boolean isEmergency) {
-        if (Objects.equals(mExpectedComponentName, accountHandle.getComponentName())) {
-            if (accountHandle.getId() != null) {
-                try {
-                    int phoneId = SubscriptionController.getInstance().getPhoneId(
-                            Integer.parseInt(accountHandle.getId()));
-                    return PhoneFactory.getPhone(phoneId);
-                } catch (NumberFormatException e) {
-                    Log.w(this, "Could not get subId from account: " + accountHandle.getId());
-                }
-            }
+        if (isEmergency) {
+            return PhoneFactory.getDefaultPhone();
         }
 
-        if (isEmergency) {
-            // If this is an emergency number and we've been asked to dial it using a PhoneAccount
-            // which does not exist, then default to whatever subscription is available currently.
-            return getFirstPhoneForEmergencyCall();
+        int subId = PhoneUtils.getSubIdForPhoneAccountHandle(accountHandle);
+        if (subId != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            int phoneId = SubscriptionController.getInstance().getPhoneId(subId);
+            return PhoneFactory.getPhone(phoneId);
         }
 
         return null;