Merge "Map answered elsewhere telephony disconnect cause to telecom equivalent." into nyc-mr1-dev
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 0a9a6a5..26b0c7a 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -58,7 +58,16 @@
     <protected-broadcast android:name= "android.intent.action.CARRIER_SIGNAL_REDIRECTED" />
     <protected-broadcast android:name= "android.intent.action.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED" />
     <protected-broadcast android:name= "android.intent.action.CARRIER_SIGNAL_PCO_VALUE" />
+    <protected-broadcast android:name= "android.intent.action.VOICEMAIL_SMS_RECEIVED" />
     <protected-broadcast android:name= "com.android.intent.isim_refresh" />
+    <protected-broadcast android:name= "com.android.ims.IMS_SERVICE_UP" />
+    <protected-broadcast android:name= "com.android.ims.IMS_SERVICE_DOWN" />
+    <protected-broadcast android:name= "com.android.ims.IMS_INCOMING_CALL" />
+    <protected-broadcast android:name= "com.android.ims.internal.uce.UCE_SERVICE_UP" />
+    <protected-broadcast android:name= "com.android.ims.internal.uce.UCE_SERVICE_DOWN" />
+    <protected-broadcast android:name= "com.android.imsconnection.DISCONNECTED" />
+    <protected-broadcast android:name= "com.android.intent.action.IMS_FEATURE_CHANGED" />
+    <protected-broadcast android:name= "com.android.intent.action.IMS_CONFIG_CHANGED" />
 
     <uses-permission android:name="android.permission.BROADCAST_STICKY" />
     <uses-permission android:name="android.permission.CALL_PHONE" />
@@ -328,7 +337,7 @@
              non-voice-capable tablets and regular phone devices. -->
         <activity android:name="MobileNetworkSettings"
             android:label="@string/settings_label"
-            android:theme="@style/SettingsLight">
+            android:theme="@style/NetworkOperatorsSettingsTheme">
             <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
                 <action android:name="android.intent.action.MAIN" />
@@ -655,23 +664,26 @@
             </intent-filter>
         </provider>
         <receiver android:name="com.android.phone.vvm.omtp.sms.OmtpMessageReceiver"
-            android:exported="true">
+            android:exported="true"
+            androidprv:systemUserOnly="true">
             <intent-filter>
                 <action android:name="android.intent.action.VOICEMAIL_SMS_RECEIVED"/>
             </intent-filter>
         </receiver>
         <receiver
             android:name="com.android.phone.vvm.omtp.SimChangeReceiver"
-            android:exported="true">
+            android:exported="true"
+            androidprv:systemUserOnly="true">
             <intent-filter>
                 <action android:name="android.telephony.action.CARRIER_CONFIG_CHANGED" />
                 <action android:name="android.intent.action.SIM_STATE_CHANGED" />
             </intent-filter>
         </receiver>
         <receiver
-            android:name="com.android.phone.vvm.omtp.OmtpBootCompletedReceiver"
+            android:name="com.android.phone.vvm.omtp.VvmBootCompletedReceiver"
             android:exported="true"
-            android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
+            android:permission="android.permission.RECEIVE_BOOT_COMPLETED"
+            androidprv:systemUserOnly="true">
             <intent-filter>
                 <action android:name="android.intent.action.BOOT_COMPLETED"/>
             </intent-filter>
@@ -679,7 +691,8 @@
         <receiver
             android:name="com.android.phone.vvm.omtp.fetch.FetchVoicemailReceiver"
             android:exported="true"
-            android:permission="com.android.voicemail.permission.READ_VOICEMAIL">
+            android:permission="com.android.voicemail.permission.READ_VOICEMAIL"
+            androidprv:systemUserOnly="true">
             <intent-filter>
                 <action android:name="android.intent.action.FETCH_VOICEMAIL" />
                     <data
@@ -691,14 +704,16 @@
         <receiver
             android:name="com.android.phone.vvm.omtp.sync.OmtpVvmSyncReceiver"
             android:exported="true"
-            android:permission="com.android.voicemail.permission.READ_VOICEMAIL">
+            android:permission="com.android.voicemail.permission.READ_VOICEMAIL"
+            androidprv:systemUserOnly="true">
             <intent-filter>
                 <action android:name="android.provider.action.SYNC_VOICEMAIL"/>
             </intent-filter>
         </receiver>
         <receiver
             android:name="com.android.phone.vvm.omtp.sync.VoicemailProviderChangeReceiver"
-            android:exported="true">
+            android:exported="true"
+            androidprv:systemUserOnly="true">
             <intent-filter>
                 <action android:name="android.intent.action.PROVIDER_CHANGED" />
                 <data
@@ -715,7 +730,8 @@
             android:name="com.android.phone.vvm.omtp.sms.OmtpProvisioningService"
             android:exported="false" />
 
-        <receiver android:name="com.android.phone.vvm.omtp.VvmPackageInstallReceiver">
+        <receiver android:name="com.android.phone.vvm.omtp.VvmPackageInstallReceiver"
+            androidprv:systemUserOnly="true">
             <intent-filter>
                 <action android:name="android.intent.action.PACKAGE_INSTALL" />
                 <action android:name="android.intent.action.PACKAGE_ADDED" />
diff --git a/proguard.flags b/proguard.flags
index 41e26a1..e8646eb 100644
--- a/proguard.flags
+++ b/proguard.flags
@@ -3,4 +3,8 @@
 -keepclassmembers class * {
 @**.VisibleForTesting *;
 }
+-keep @**.NeededForTesting class *
+-keepclassmembers class * {
+@**.NeededForTesting *;
+}
 -verbose
diff --git a/res/values-bn-rBD/strings.xml b/res/values-bn-rBD/strings.xml
index d3106d4..5973fca 100644
--- a/res/values-bn-rBD/strings.xml
+++ b/res/values-bn-rBD/strings.xml
@@ -151,7 +151,7 @@
     <string name="vm_change_pin_new_pin" msgid="5412922262839438097">"নতুন পিন"</string>
     <string name="vm_change_pin_progress_message" msgid="6727847908454506025">"পিন পরিবর্তন করুন"</string>
     <string name="vm_change_pin_error_too_short" msgid="5974971097302710497">"নতুন পিনটি খুবই ছোট৷"</string>
-    <string name="vm_change_pin_error_too_long" msgid="8476870806115051865">"নতুন পিনটি খুবই দীর্ঘ৷"</string>
+    <string name="vm_change_pin_error_too_long" msgid="8476870806115051865">"নতুন পিনটি খুবই বড়৷"</string>
     <string name="vm_change_pin_error_too_weak" msgid="7883744811891784882">"নতুন পিনটি খুবই দুর্বল৷ একটি শক্তিশালী পাসওয়ার্ডে ধারাবাহিক ক্রম বা পুনরাবৃত্ত সংখ্যা থাকা উচিৎ নয়৷"</string>
     <string name="vm_change_pin_error_mismatch" msgid="2754685537970757317">"পুরানো পিন মিলছে না৷"</string>
     <string name="vm_change_pin_error_invalid" msgid="3972205462701668653">"নতুন পিনে অবৈধ অক্ষর রয়েছে৷"</string>
diff --git a/res/values-eu-rES/strings.xml b/res/values-eu-rES/strings.xml
index df93f33..e784ddc 100644
--- a/res/values-eu-rES/strings.xml
+++ b/res/values-eu-rES/strings.xml
@@ -152,9 +152,9 @@
     <string name="vm_change_pin_progress_message" msgid="6727847908454506025">"PIN kodea aldatzen"</string>
     <string name="vm_change_pin_error_too_short" msgid="5974971097302710497">"Laburregia da PIN kode berria."</string>
     <string name="vm_change_pin_error_too_long" msgid="8476870806115051865">"Luzeegia da PIN kode berria."</string>
-    <string name="vm_change_pin_error_too_weak" msgid="7883744811891784882">"Ahulegia da PIN kode berria. Pasahitz sendo batek ez luke eduki beharko zenbaki-segida edo errepikatutako zenbakirik."</string>
+    <string name="vm_change_pin_error_too_weak" msgid="7883744811891784882">"Ahulegia da PIN kode berria. Pasahitza sendoa izan dadin, ez du izan behar zenbaki-segidarik edo errepikatutako zenbakirik."</string>
     <string name="vm_change_pin_error_mismatch" msgid="2754685537970757317">"PIN kode zaharra ez dator bat."</string>
-    <string name="vm_change_pin_error_invalid" msgid="3972205462701668653">"Balio ez duten karaktereak ditu PIN kode berriak"</string>
+    <string name="vm_change_pin_error_invalid" msgid="3972205462701668653">"Balio ez duten karaktereak ditu PIN kode berriak."</string>
     <string name="vm_change_pin_error_system_error" msgid="6610603326230000207">"Ezin da aldatu PIN kodea"</string>
     <string name="mobile_networks" msgid="2843854043339307375">"Sare mugikorraren ezarpenak"</string>
     <string name="label_available" msgid="1181658289009300430">"Sare erabilgarriak"</string>
diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy-rAM/strings.xml
index be7bb8a..41628c9 100644
--- a/res/values-hy-rAM/strings.xml
+++ b/res/values-hy-rAM/strings.xml
@@ -230,11 +230,11 @@
     <!-- String.format failed for translation -->
     <!-- no translation found for throttle_data_usage_subtext (6029276011123694701) -->
     <skip />
-    <string name="throttle_data_rate_reduced_subtext" msgid="7492763592720107737">"<xliff:g id="USED_0">%1$s</xliff:g> առավելագույնը գերազանցվել է\nՏվյալների արժեքը նվազել է մինչև <xliff:g id="USED_1">%2$d</xliff:g> ԿԲ/վ"</string>
+    <string name="throttle_data_rate_reduced_subtext" msgid="7492763592720107737">"<xliff:g id="USED_0">%1$s</xliff:g> առավելագույնը գերազանցվել է\nՏվյալների արժեքը նվազել է մինչև <xliff:g id="USED_1">%2$d</xliff:g> կԲ/վ"</string>
     <!-- String.format failed for translation -->
     <!-- no translation found for throttle_time_frame_subtext (7732763021560399960) -->
     <skip />
-    <string name="throttle_rate_subtext" msgid="2149102656120726855">"Տվյալների ծավալը կնվազի մինչև <xliff:g id="USED">%1$d</xliff:g> ԿԲ/վ, եթե տվյալների օգտագործման սահմանաչափը գերազանցվի"</string>
+    <string name="throttle_rate_subtext" msgid="2149102656120726855">"Տվյալների ծավալը կնվազի մինչև <xliff:g id="USED">%1$d</xliff:g> կԲ/վ, եթե տվյալների օգտագործման սահմանաչափը գերազանցվի"</string>
     <string name="throttle_help_subtext" msgid="3633091498168446044">"Լրացուցիչ տեղեկություններ ձեր օպերատորի բջջային ցանցի տվյալների օգտագործման քաղաքականության մասին"</string>
     <string name="cell_broadcast_sms" msgid="5584192824053625842">"Բջջային հեռարձակման SMS"</string>
     <string name="enable_disable_cell_bc_sms" msgid="4851147873691392255">"Բջջային հեռարձակման SMS"</string>
@@ -392,7 +392,7 @@
     <string name="simContacts_emptyLoading" msgid="2203331234764498011">"Ընթերցում է SIM քարտից..."</string>
     <string name="simContacts_empty" msgid="5270660846489561932">"Ձեր SIM քարտում կոնտակտներ չկան:"</string>
     <string name="simContacts_title" msgid="1861472842524839921">"Ընտրեք կոնտակտները ներմուծման համար"</string>
-    <string name="simContacts_airplaneMode" msgid="5254946758982621072">"Կոնտակտները SIM քարտից ներմուծելու համար անջատեք ինքնաթիռային ռեժիմը:"</string>
+    <string name="simContacts_airplaneMode" msgid="5254946758982621072">"Կոնտակտները SIM քարտից ներմուծելու համար անջատեք Ինքնաթիռի ռեժիմը:"</string>
     <string name="enable_pin" msgid="5422767284133234860">"Միացնել/անջատել SIM PIN-ը"</string>
     <string name="change_pin" msgid="9174186126330785343">"Փոխել SIM PIN-ը"</string>
     <string name="enter_pin_text" msgid="8532615714751931951">"SIM PIN՝"</string>
@@ -443,8 +443,8 @@
     <string name="notification_voicemail_no_vm_number" msgid="760963466895609716">"Ձայնային փոստի համարն անհայտ է"</string>
     <string name="notification_network_selection_title" msgid="4224455487793492772">"Ծառայություններ չկան"</string>
     <string name="notification_network_selection_text" msgid="2607085729661923269">"Ընտրված ցանցը (<xliff:g id="OPERATOR_NAME">%s</xliff:g>) անհասանելի է"</string>
-    <string name="incall_error_power_off" msgid="2947938060513306698">"Զանգ կատարելու համար անջատեք ինքնաթիռային ռեժիմը:"</string>
-    <string name="incall_error_power_off_wfc" msgid="8711428920632416575">"Զանգ կատարելու համար անջատեք ինքնաթիռային ռեժիմը կամ միացեք անլար ցանցին:"</string>
+    <string name="incall_error_power_off" msgid="2947938060513306698">"Զանգ կատարելու համար անջատեք Ինքնաթիռի ռեժիմը:"</string>
+    <string name="incall_error_power_off_wfc" msgid="8711428920632416575">"Զանգ կատարելու համար անջատեք Ինքնաթիռի ռեժիմը կամ միացեք անլար ցանցին:"</string>
     <string name="incall_error_ecm_emergency_only" msgid="738708660612388692">"Սովորական զանգ կատարելու համար դուրս եկեք արտակարգ իրավիճակների հետզանգի ռեժիմից:"</string>
     <string name="incall_error_emergency_only" msgid="4678640422710818317">"Ցանցում գրանցված չէ:"</string>
     <string name="incall_error_out_of_service" msgid="4100065333878929223">"Բջջային ցանցն անհասանելի է:"</string>
@@ -464,7 +464,7 @@
     <string name="emergency_enable_radio_dialog_title" msgid="4627849966634578257">"Արտակարգ իրավիճակների զանգ"</string>
     <string name="emergency_enable_radio_dialog_message" msgid="207613549344420291">"Ռադիոն միացվում է..."</string>
     <string name="emergency_enable_radio_dialog_retry" msgid="5960061579996526883">"Ծառայությունը մատչելի չէ: Նորից փորձեք…"</string>
-    <string name="radio_off_during_emergency_call" msgid="2535800034010306830">"Արտակարգ իրավիճակների զանգի ժամանակ հնարավոր չէ մտնել ինքնաթիռային ռեժիմ:"</string>
+    <string name="radio_off_during_emergency_call" msgid="2535800034010306830">"Արտակարգ իրավիճակների զանգի ժամանակ հնարավոր չէ մտնել ինքնաթիռի ռեժիմ:"</string>
     <string name="dial_emergency_error" msgid="1509085166367420355">"Հնարավոր չէ զանգել: <xliff:g id="NON_EMERGENCY_NUMBER">%s</xliff:g> համարը արտակարգ իրավիճակի համար չէ:"</string>
     <string name="dial_emergency_empty_error" msgid="9130194953830414638">"Հնարավոր չէ զանգել: Հավաքեք արտակարգ իրավիճակի որևէ համար:"</string>
     <string name="dialerKeyboardHintText" msgid="9192914825413747792">"Օգտագործեք ստեղնաշարը՝ համարհավաքման համար"</string>
diff --git a/res/values-kk-rKZ/strings.xml b/res/values-kk-rKZ/strings.xml
index 57314a4..eb949b9 100644
--- a/res/values-kk-rKZ/strings.xml
+++ b/res/values-kk-rKZ/strings.xml
@@ -149,7 +149,7 @@
     <string name="voicemail_default" msgid="2001233554889016880">"Операторыңыз"</string>
     <string name="vm_change_pin_old_pin" msgid="7295220109886682573">"Ескі PIN"</string>
     <string name="vm_change_pin_new_pin" msgid="5412922262839438097">"Жаңа PIN"</string>
-    <string name="vm_change_pin_progress_message" msgid="6727847908454506025">"PIN өзгерту"</string>
+    <string name="vm_change_pin_progress_message" msgid="6727847908454506025">"PIN код өзгертілуде"</string>
     <string name="vm_change_pin_error_too_short" msgid="5974971097302710497">"Жаңа PIN код тым қысқа."</string>
     <string name="vm_change_pin_error_too_long" msgid="8476870806115051865">"Жаңа PIN код тым ұзын."</string>
     <string name="vm_change_pin_error_too_weak" msgid="7883744811891784882">"Жаңа PIN код тым әлсіз. Күшті құпия сөзде үздіксіз реттік немесе қайталанатын таңбалар болмауы тиіс."</string>
diff --git a/res/values-ky-rKG/strings.xml b/res/values-ky-rKG/strings.xml
index b03984c..3e20bef 100644
--- a/res/values-ky-rKG/strings.xml
+++ b/res/values-ky-rKG/strings.xml
@@ -152,7 +152,7 @@
     <string name="vm_change_pin_progress_message" msgid="6727847908454506025">"PIN код өзгөртүлүүдө"</string>
     <string name="vm_change_pin_error_too_short" msgid="5974971097302710497">"Жаңы PIN код өтө эле кыска."</string>
     <string name="vm_change_pin_error_too_long" msgid="8476870806115051865">"Жаңы PIN код өтө эле узун."</string>
-    <string name="vm_change_pin_error_too_weak" msgid="7883744811891784882">"Жаңы PIN код өтө эле туруксуз. Туруктуу сырсөз үзгүлтүксүз катардан же кайталанган сандардан турбашы керек."</string>
+    <string name="vm_change_pin_error_too_weak" msgid="7883744811891784882">"Жаңы PIN код өтө эле жөнөкөй. Сырсөз күчтүү болушу үчүн анда сандар үзгүлтүксүз катардан турбашы же сандар кайталанбашы керек."</string>
     <string name="vm_change_pin_error_mismatch" msgid="2754685537970757317">"Эски PIN код дал келген жок."</string>
     <string name="vm_change_pin_error_invalid" msgid="3972205462701668653">"Жаңы PIN коддо жараксыз белгилер бар."</string>
     <string name="vm_change_pin_error_system_error" msgid="6610603326230000207">"PIN код өзгөртүлгөн жок"</string>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 8e7498c..6c87001 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -85,7 +85,7 @@
     <string name="additional_cdma_call_settings" msgid="8628958775721886909">"Definições adicionais de chamadas CDMA"</string>
     <string name="sum_cdma_call_settings" msgid="284753265979035549">"Definições adicionais de chamadas apenas CDMA"</string>
     <string name="labelNwService" msgid="4699970172021870983">"Definições do serviço de rede"</string>
-    <string name="labelCallerId" msgid="3888899447379069198">"ID do autor da chamada"</string>
+    <string name="labelCallerId" msgid="3888899447379069198">"Identificação de chamadas"</string>
     <string name="sum_loading_settings" msgid="1826692909391168620">"A carregar as definições..."</string>
     <string name="sum_hide_caller_id" msgid="1071407020290873782">"Ocultar o número em chamadas efectuadas"</string>
     <string name="sum_show_caller_id" msgid="6768534125447290401">"Número apresentado em chamadas efectuadas"</string>
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 67572ed..870d692 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -54,9 +54,4 @@
 
     <color name="dialer_dialpad_touch_tint">#330288d1</color>
     <color name="floating_action_button_touch_tint">#80ffffff</color>
-
-    <color name="network_operators_color_primary">#ff263238</color>
-    <color name="network_operators_color_primary_dark">#ff21272b</color>
-
-    <color name="emergency_dialer_background">#ff263238</color>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 9d2d47f..057352d 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -153,13 +153,6 @@
         <item name="android:layout_marginEnd">5dip</item>
     </style>
 
-    <!-- Theme for the activity com.android.phone.Settings, which is the
-         "Mobile network settings" screen (used on non-voice-capable
-         tablets as well as regular phone devices.) -->
-    <style name="Theme.Settings" parent="@android:style/Theme.Holo.DialogWhenLarge">
-        <item name="android:windowCloseOnTouchOutside">true</item>
-    </style>
-
     <style name="SettingsLight" parent="android:Theme.Material.Light">
         <item name="android:windowBackground">@color/phone_settings_background_color</item>
         <item name="android:windowContentOverlay">@null</item>
@@ -186,11 +179,7 @@
         <item name="android:textColor">?android:attr/textColorPrimaryInverseDisableOnly</item>
     </style>
 
-    <style name="NetworkOperatorsSettingsTheme" parent="@android:style/Theme.Material.Light">
-        <item name="android:actionBarTheme">@android:style/ThemeOverlay.Material.Dark.ActionBar</item>
-        <item name="android:colorPrimary">@color/network_operators_color_primary</item>
-        <item name="android:colorPrimaryDark">@color/network_operators_color_primary_dark</item>
-    </style>
+    <style name="NetworkOperatorsSettingsTheme" parent="@android:style/Theme.DeviceDefault.Settings" />
 
     <style name="Empty" parent="@android:style/Theme.Material.Light">
         <item name="android:windowIsTranslucent">true</item>
@@ -256,10 +245,9 @@
         <item name="android:src">@drawable/overflow_menu</item>
     </style>
 
-    <style name="EmergencyDialerTheme" parent="@android:style/Theme.Material.NoActionBar">
-        <item name="android:colorPrimary">@color/emergency_dialer_background</item>
-        <item name="android:colorPrimaryDark">@color/emergency_dialer_background</item>
-        <item name="android:windowBackground">@color/emergency_dialer_background</item>
+    <style name="EmergencyDialerTheme" parent="@*android:style/Theme.DeviceDefault.Settings.Dark.NoActionBar">
+        <item name="android:colorPrimaryDark">?android:attr/colorPrimary</item>
+        <item name="android:windowBackground">?android:attr/colorPrimary</item>
         <item name="android:statusBarColor">@android:color/transparent</item>
         <item name="android:navigationBarColor">@android:color/transparent</item>
         <item name="android:homeAsUpIndicator">@drawable/ic_back_arrow</item>
@@ -296,8 +284,4 @@
         <item name="android:backgroundDimEnabled">false</item>
     </style>
 
-    <style name="Theme.Material.Settings" parent="@android:style/Theme.Material.Settings">
-        <item name="@*android:actionBarSize">56dip</item>
-        <item name="preferenceBackgroundColor">@drawable/preference_background</item>
-    </style>
 </resources>
diff --git a/res/xml/vvm_config.xml b/res/xml/vvm_config.xml
index 79edaa6..d55fdb2 100644
--- a/res/xml/vvm_config.xml
+++ b/res/xml/vvm_config.xml
@@ -134,5 +134,6 @@
     <string name="vvm_type_string">vvm_type_vvm3</string>
     <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"/>
   </pbundle_as_map>
 </list>
\ No newline at end of file
diff --git a/src/com/android/phone/CallFeaturesSetting.java b/src/com/android/phone/CallFeaturesSetting.java
index d74558f..33aba17 100644
--- a/src/com/android/phone/CallFeaturesSetting.java
+++ b/src/com/android/phone/CallFeaturesSetting.java
@@ -300,7 +300,8 @@
             } else {
                 prefSet.removePreference(wifiCallingSettings);
             }
-        } else if (!ImsManager.isWfcEnabledByPlatform(mPhone.getContext())) {
+        } else if (!ImsManager.isWfcEnabledByPlatform(mPhone.getContext()) ||
+                !ImsManager.isWfcProvisionedOnDevice(mPhone.getContext())) {
             prefSet.removePreference(wifiCallingSettings);
         } else {
             int resId = com.android.internal.R.string.wifi_calling_off_summary;
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
index 3204a9f..5a40322 100644
--- a/src/com/android/phone/CarrierConfigLoader.java
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -665,9 +665,11 @@
         // TODO: Check that the calling packages is privileged for subId specifically.
         int privilegeStatus = TelephonyManager.from(mContext).checkCarrierPrivilegesForPackage(
                 callingPackageName);
+        // Requires the calling app to be either a carrier privileged app or
+        // system privileged app with MODIFY_PHONE_STATE permission.
         if (privilegeStatus != TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
-            throw new SecurityException(
-                    "Package is not privileged for subId=" + subId + ": " + callingPackageName);
+            mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE,
+                    "Require carrier privileges or MODIFY_PHONE_STATE permission.");
         }
 
         // This method should block until deleting has completed, so that an error which prevents us
diff --git a/src/com/android/phone/DumpsysHandler.java b/src/com/android/phone/DumpsysHandler.java
new file mode 100644
index 0000000..d2ae38f
--- /dev/null
+++ b/src/com/android/phone/DumpsysHandler.java
@@ -0,0 +1,21 @@
+
+package com.android.phone;
+
+import android.content.Context;
+
+import com.android.phone.vvm.omtp.utils.VvmDumpHandler;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Handles "adb shell dumpsys phone" and bug report dump.
+ */
+public class DumpsysHandler {
+
+    public static void dump(Context context, FileDescriptor fd, PrintWriter writer,
+            String[] args) {
+        // Dump OMTP visual voicemail log.
+        VvmDumpHandler.dump(context, fd, writer, args);
+    }
+}
diff --git a/src/com/android/phone/MobileNetworkSettings.java b/src/com/android/phone/MobileNetworkSettings.java
index 404c976..fd4815e 100644
--- a/src/com/android/phone/MobileNetworkSettings.java
+++ b/src/com/android/phone/MobileNetworkSettings.java
@@ -421,7 +421,6 @@
     @Override
     protected void onCreate(Bundle icicle) {
         if (DBG) log("onCreate:+");
-        setTheme(R.style.Theme_Material_Settings);
         super.onCreate(icicle);
 
         mHandler = new MyHandler();
diff --git a/src/com/android/phone/NeededForTesting.java b/src/com/android/phone/NeededForTesting.java
new file mode 100644
index 0000000..576598b
--- /dev/null
+++ b/src/com/android/phone/NeededForTesting.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.phone;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@Retention(RetentionPolicy.SOURCE)
+public @interface NeededForTesting {
+
+}
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
index 783878c..808a5d6 100644
--- a/src/com/android/phone/PhoneGlobals.java
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -637,8 +637,53 @@
         notifier.updateCallNotifierRegistrationsAfterRadioTechnologyChange();
     }
 
-    private void handleAirplaneModeChange(int newMode) {
-        if (newMode == AIRPLANE_ON) {
+    private void handleAirplaneModeChange(Context context, int newMode) {
+        int cellState = Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.CELL_ON, PhoneConstants.CELL_ON_FLAG);
+        boolean isAirplaneNewlyOn = (newMode == 1);
+        switch (cellState) {
+            case PhoneConstants.CELL_OFF_FLAG:
+                // Airplane mode does not affect the cell radio if user
+                // has turned it off.
+                break;
+            case PhoneConstants.CELL_ON_FLAG:
+                maybeTurnCellOff(context, isAirplaneNewlyOn);
+                break;
+            case PhoneConstants.CELL_OFF_DUE_TO_AIRPLANE_MODE_FLAG:
+                maybeTurnCellOn(context, isAirplaneNewlyOn);
+                break;
+        }
+    }
+
+    /*
+     * Returns true if the radio must be turned off when entering airplane mode.
+     */
+    private boolean isCellOffInAirplaneMode(Context context) {
+        String airplaneModeRadios = Settings.Global.getString(context.getContentResolver(),
+                Settings.Global.AIRPLANE_MODE_RADIOS);
+        return airplaneModeRadios == null
+                || airplaneModeRadios.contains(Settings.Global.RADIO_CELL);
+    }
+
+    private void setRadioPowerOff(Context context) {
+        Log.i(LOG_TAG, "Turning radio off - airplane");
+        Settings.Global.putInt(context.getContentResolver(), Settings.Global.CELL_ON,
+                 PhoneConstants.CELL_OFF_DUE_TO_AIRPLANE_MODE_FLAG);
+        Settings.Global.putInt(getContentResolver(), Settings.Global.ENABLE_CELLULAR_ON_BOOT, 0);
+        PhoneUtils.setRadioPower(false);
+    }
+
+    private void setRadioPowerOn(Context context) {
+        Log.i(LOG_TAG, "Turning radio on - airplane");
+        Settings.Global.putInt(context.getContentResolver(), Settings.Global.CELL_ON,
+                PhoneConstants.CELL_ON_FLAG);
+        Settings.Global.putInt(getContentResolver(), Settings.Global.ENABLE_CELLULAR_ON_BOOT,
+                1);
+        PhoneUtils.setRadioPower(true);
+    }
+
+    private void maybeTurnCellOff(Context context, boolean isAirplaneNewlyOn) {
+        if (isAirplaneNewlyOn) {
             // If we are trying to turn off the radio, make sure there are no active
             // emergency calls.  If there are, switch airplane mode back to off.
             if (PhoneUtils.isInEmergencyCall(mCM)) {
@@ -647,13 +692,17 @@
                 Toast.makeText(this, R.string.radio_off_during_emergency_call, Toast.LENGTH_LONG)
                         .show();
                 Log.i(LOG_TAG, "Ignoring airplane mode: emergency call. Turning airplane off");
+            } else if (isCellOffInAirplaneMode(context)) {
+                setRadioPowerOff(context);
             } else {
-                Log.i(LOG_TAG, "Turning radio off - airplane");
-                PhoneUtils.setRadioPower(false);
+                Log.i(LOG_TAG, "Ignoring airplane mode: settings prevent cell radio power off");
             }
-        } else {
-            Log.i(LOG_TAG, "Turning radio on - airplane");
-            PhoneUtils.setRadioPower(true);
+        }
+    }
+
+    private void maybeTurnCellOn(Context context, boolean isAirplaneNewlyOn) {
+        if (!isAirplaneNewlyOn) {
+            setRadioPowerOn(context);
         }
     }
 
@@ -671,7 +720,7 @@
                 if (airplaneMode != AIRPLANE_OFF) {
                     airplaneMode = AIRPLANE_ON;
                 }
-                handleAirplaneModeChange(airplaneMode);
+                handleAirplaneModeChange(context, airplaneMode);
             } else if (action.equals(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
                 int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
                         SubscriptionManager.INVALID_SUBSCRIPTION_ID);
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 2ace8e1..d050576 100644
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -85,6 +85,8 @@
 import com.android.internal.util.HexDump;
 import com.android.phone.settings.VoicemailNotificationSettingsUtil;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -3289,4 +3291,45 @@
         }
     }
 
+    /**
+     * Called when "adb shell dumpsys phone" is invoked. Dump is also automatically invoked when a
+     * bug report is being generated.
+     */
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+        if (mPhone.getContext().checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
+                != PackageManager.PERMISSION_GRANTED) {
+            writer.println("Permission Denial: can't dump Phone from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + "without permission "
+                    + android.Manifest.permission.DUMP);
+            return;
+        }
+        DumpsysHandler.dump(mPhone.getContext(), fd, writer, args);
+    }
+
+    /**
+     * Get aggregated video call data usage from all subscriptions since boot.
+     * @return total data usage in bytes
+     * {@hide}
+     */
+    @Override
+    public long getVtDataUsage() {
+        mApp.enforceCallingOrSelfPermission(android.Manifest.permission.READ_NETWORK_USAGE_HISTORY,
+                null);
+
+        // NetworkStatsService keeps tracking the active network interface and identity. It will
+        // record the delta with the corresponding network identity. What we need to do here is
+        // returning total video call data usage from all subscriptions since boot.
+
+        // TODO: Add sub id support in the future. We'll need it when we support DSDA and
+        // simultaneous VT calls.
+        final Phone[] phones = PhoneFactory.getPhones();
+        long total = 0;
+        for (Phone phone : phones) {
+            total += phone.getVtDataUsage();
+        }
+        return total;
+    }
 }
diff --git a/src/com/android/phone/PhoneUtils.java b/src/com/android/phone/PhoneUtils.java
index 4dd7d0b..9f70349 100644
--- a/src/com/android/phone/PhoneUtils.java
+++ b/src/com/android/phone/PhoneUtils.java
@@ -64,7 +64,6 @@
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.telephony.sip.SipPhone;
 import com.android.phone.CallGatewayManager.RawGatewayInfo;
-import com.android.services.telephony.TelephonyConnectionService;
 
 import java.util.Arrays;
 import java.util.List;
@@ -2442,7 +2441,7 @@
         return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
     }
 
-    static Phone getPhoneForPhoneAccountHandle(PhoneAccountHandle handle) {
+    public static Phone getPhoneForPhoneAccountHandle(PhoneAccountHandle handle) {
         if (handle != null && handle.getComponentName().equals(getPstnConnectionServiceName())) {
             return getPhoneFromIccId(handle.getId());
         }
diff --git a/src/com/android/phone/common/mail/internet/MimeUtility.java b/src/com/android/phone/common/mail/internet/MimeUtility.java
index ba5036f..7402a4c 100644
--- a/src/com/android/phone/common/mail/internet/MimeUtility.java
+++ b/src/com/android/phone/common/mail/internet/MimeUtility.java
@@ -19,7 +19,6 @@
 import android.util.Base64;
 import android.util.Base64DataException;
 import android.util.Base64InputStream;
-import android.util.Log;
 
 import com.android.phone.common.mail.Body;
 import com.android.phone.common.mail.BodyPart;
@@ -27,6 +26,7 @@
 import com.android.phone.common.mail.MessagingException;
 import com.android.phone.common.mail.Multipart;
 import com.android.phone.common.mail.Part;
+import com.android.phone.vvm.omtp.VvmLog;
 
 import org.apache.commons.io.IOUtils;
 import org.apache.james.mime4j.codec.EncoderUtil;
@@ -267,14 +267,14 @@
              * If we are not able to process the body there's nothing we can do about it. Return
              * null and let the upper layers handle the missing content.
              */
-            Log.e(LOG_TAG, "Unable to getTextFromPart " + oom.toString());
+            VvmLog.e(LOG_TAG, "Unable to getTextFromPart " + oom.toString());
         }
         catch (Exception e) {
             /*
              * If we are not able to process the body there's nothing we can do about it. Return
              * null and let the upper layers handle the missing content.
              */
-            Log.e(LOG_TAG, "Unable to getTextFromPart " + e.toString());
+            VvmLog.e(LOG_TAG, "Unable to getTextFromPart " + e.toString());
         }
         return null;
     }
diff --git a/src/com/android/phone/common/mail/store/ImapConnection.java b/src/com/android/phone/common/mail/store/ImapConnection.java
index de40f2c..0360e3e 100644
--- a/src/com/android/phone/common/mail/store/ImapConnection.java
+++ b/src/com/android/phone/common/mail/store/ImapConnection.java
@@ -15,7 +15,6 @@
  */
 package com.android.phone.common.mail.store;
 
-import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Base64;
 
@@ -180,18 +179,48 @@
             }
         } catch (ImapException ie) {
             LogUtils.d(TAG, "ImapException", ie);
-            final String status = ie.getStatus();
-            final String code = ie.getResponseCode();
-            final String alertText = ie.getAlertText();
+            String status = ie.getStatus();
+            String statusMessage = ie.getStatusMessage();
+            String alertText = ie.getAlertText();
 
-            // if the response code indicates expired or bad credentials, throw a special exception
-            if (ImapConstants.AUTHENTICATIONFAILED.equals(code) ||
-                    ImapConstants.EXPIRED.equals(code) ||
-                    (ImapConstants.NO.equals(status) && TextUtils.isEmpty(code))) {
-                mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_BAD_IMAP_CREDENTIAL);
+            if (ImapConstants.NO.equals(status)) {
+                switch (statusMessage) {
+                    case ImapConstants.NO_UNKNOWN_USER:
+                        mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_AUTH_UNKNOWN_USER);
+                        break;
+                    case ImapConstants.NO_UNKNOWN_CLIENT:
+                        mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_AUTH_UNKNOWN_DEVICE);
+                        break;
+                    case ImapConstants.NO_INVALID_PASSWORD:
+                        mImapStore.getImapHelper()
+                                .handleEvent(OmtpEvents.DATA_AUTH_INVALID_PASSWORD);
+                        break;
+                    case ImapConstants.NO_MAILBOX_NOT_INITIALIZED:
+                        mImapStore.getImapHelper()
+                                .handleEvent(OmtpEvents.DATA_AUTH_MAILBOX_NOT_INITIALIZED);
+                        break;
+                    case ImapConstants.NO_SERVICE_IS_NOT_PROVISIONED:
+                        mImapStore.getImapHelper()
+                                .handleEvent(OmtpEvents.DATA_AUTH_SERVICE_NOT_PROVISIONED);
+                        break;
+                    case ImapConstants.NO_SERVICE_IS_NOT_ACTIVATED:
+                        mImapStore.getImapHelper()
+                                .handleEvent(OmtpEvents.DATA_AUTH_SERVICE_NOT_ACTIVATED);
+                        break;
+                    case ImapConstants.NO_USER_IS_BLOCKED:
+                        mImapStore.getImapHelper()
+                                .handleEvent(OmtpEvents.DATA_AUTH_USER_IS_BLOCKED);
+                        break;
+                    case ImapConstants.NO_APPLICATION_ERROR:
+                        mImapStore.getImapHelper()
+                                .handleEvent(OmtpEvents.DATA_REJECTED_SERVER_RESPONSE);
+                    default:
+                        mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_BAD_IMAP_CREDENTIAL);
+                }
                 throw new AuthenticationFailedException(alertText, ie);
             }
 
+            mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_REJECTED_SERVER_RESPONSE);
             throw new MessagingException(alertText, ie);
         }
     }
@@ -359,16 +388,11 @@
         if (!(response.isOk() || response.isContinuationRequest())) {
             final String toString = response.toString();
             final String status = response.getStatusOrEmpty().getString();
+            final String statusMessage = response.getStatusResponseTextOrEmpty().getString();
             final String alert = response.getAlertTextOrEmpty().getString();
             final String responseCode = response.getResponseCodeOrEmpty().getString();
             destroyResponses();
-            mImapStore.getImapHelper().handleEvent(OmtpEvents.DATA_REJECTED_SERVER_RESPONSE);
-            // if the response code indicates an error occurred within the server, indicate that
-            if (ImapConstants.UNAVAILABLE.equals(responseCode)) {
-
-                throw new MessagingException(MessagingException.SERVER_ERROR, alert);
-            }
-            throw new ImapException(toString, status, alert, responseCode);
+            throw new ImapException(toString, status, statusMessage, alert, responseCode);
         }
         return responses;
     }
diff --git a/src/com/android/phone/common/mail/store/ImapStore.java b/src/com/android/phone/common/mail/store/ImapStore.java
index c8095e5..179d0f2 100644
--- a/src/com/android/phone/common/mail/store/ImapStore.java
+++ b/src/com/android/phone/common/mail/store/ImapStore.java
@@ -130,13 +130,15 @@
         private static final long serialVersionUID = 1L;
 
         private final String mStatus;
+        private final String mStatusMessage;
         private final String mAlertText;
         private final String mResponseCode;
 
-        public ImapException(String message, String status, String alertText,
+        public ImapException(String message, String status, String statusMessage, String alertText,
                 String responseCode) {
             super(message);
             mStatus = status;
+            mStatusMessage = statusMessage;
             mAlertText = alertText;
             mResponseCode = responseCode;
         }
@@ -145,6 +147,10 @@
             return mStatus;
         }
 
+        public String getStatusMessage() {
+            return mStatusMessage;
+        }
+
         public String getAlertText() {
             return mAlertText;
         }
diff --git a/src/com/android/phone/common/mail/store/imap/DigestMd5Utils.java b/src/com/android/phone/common/mail/store/imap/DigestMd5Utils.java
index e6376a3..f78dbdf 100644
--- a/src/com/android/phone/common/mail/store/imap/DigestMd5Utils.java
+++ b/src/com/android/phone/common/mail/store/imap/DigestMd5Utils.java
@@ -19,12 +19,12 @@
 import android.annotation.Nullable;
 import android.util.ArrayMap;
 import android.util.Base64;
-import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.phone.common.mail.MailTransport;
 import com.android.phone.common.mail.MessagingException;
 import com.android.phone.common.mail.store.ImapStore;
+import com.android.phone.vvm.omtp.VvmLog;
 
 import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
@@ -244,7 +244,7 @@
                     }
                 }
             } catch (IndexOutOfBoundsException e) {
-                Log.e(TAG, e.toString());
+                VvmLog.e(TAG, e.toString());
                 return null;
             }
             return mResult;
diff --git a/src/com/android/phone/common/mail/store/imap/ImapConstants.java b/src/com/android/phone/common/mail/store/imap/ImapConstants.java
index 9e6e247..a2eab13 100644
--- a/src/com/android/phone/common/mail/store/imap/ImapConstants.java
+++ b/src/com/android/phone/common/mail/store/imap/ImapConstants.java
@@ -98,6 +98,32 @@
     public static final String NIL = "NIL";
 
     /**
+     * NO responses
+     */
+    public static final String NO_COMMAND_NOT_ALLOWED = "command not allowed";
+    public static final String NO_RESERVATION_FAILED = "reservation failed";
+    public static final String NO_APPLICATION_ERROR = "application error";
+    public static final String NO_INVALID_PARAMETER = "invalid parameter";
+    public static final String NO_INVALID_COMMAND = "invalid command";
+    public static final String NO_UNKNOWN_COMMAND = "unknown command";
+    // AUTHENTICATE
+    // The subscriber can not be located in the system.
+    public static final String NO_UNKNOWN_USER = "unknown user";
+    // The Client Type or Protocol Version is unknown.
+    public static final String NO_UNKNOWN_CLIENT = "unknown client";
+    // The password received from the client does not match the password defined in the subscriber's profile.
+    public static final String NO_INVALID_PASSWORD = "invalid password";
+    // The subscriber's mailbox has not yet been initialised via the TUI
+    public static final String NO_MAILBOX_NOT_INITIALIZED = "mailbox not initialized";
+    // The subscriber has not been provisioned for the VVM service.
+    public static final String NO_SERVICE_IS_NOT_PROVISIONED =
+            "service is not provisioned";
+    // The subscriber is provisioned for the VVM service but the VVM service is currently not active
+    public static final String NO_SERVICE_IS_NOT_ACTIVATED = "service is not activated";
+    // The Voice Mail Blocked flag in the subscriber's profile is set to YES.
+    public static final String NO_USER_IS_BLOCKED = "user is blocked";
+
+    /**
      * extensions
      */
     public static final String GETQUOTA = "GETQUOTA";
@@ -105,11 +131,6 @@
     public static final String QUOTAROOT = "QUOTAROOT";
     public static final String QUOTA = "QUOTA";
 
-    /** response codes within IMAP responses */
-    public static final String EXPIRED = "EXPIRED";
-    public static final String AUTHENTICATIONFAILED = "AUTHENTICATIONFAILED";
-    public static final String UNAVAILABLE = "UNAVAILABLE";
-
     /**
      * capabilities
      */
diff --git a/src/com/android/phone/common/mail/store/imap/ImapMemoryLiteral.java b/src/com/android/phone/common/mail/store/imap/ImapMemoryLiteral.java
index aac66c2..4811590 100644
--- a/src/com/android/phone/common/mail/store/imap/ImapMemoryLiteral.java
+++ b/src/com/android/phone/common/mail/store/imap/ImapMemoryLiteral.java
@@ -16,9 +16,8 @@
 
 package com.android.phone.common.mail.store.imap;
 
-import android.util.Log;
-
 import com.android.phone.common.mail.FixedLengthInputStream;
+import com.android.phone.vvm.omtp.VvmLog;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
@@ -45,7 +44,7 @@
             pos += read;
         }
         if (pos != mData.length) {
-            Log.w(TAG, "");
+            VvmLog.w(TAG, "length mismatch");
         }
     }
 
@@ -60,7 +59,7 @@
         try {
             return new String(mData, "US-ASCII");
         } catch (UnsupportedEncodingException e) {
-            Log.e(TAG, "Unsupported encoding: ", e);
+            VvmLog.e(TAG, "Unsupported encoding: ", e);
         }
         return null;
     }
diff --git a/src/com/android/phone/common/mail/store/imap/ImapResponseParser.java b/src/com/android/phone/common/mail/store/imap/ImapResponseParser.java
index d0413df..a6d2df6 100644
--- a/src/com/android/phone/common/mail/store/imap/ImapResponseParser.java
+++ b/src/com/android/phone/common/mail/store/imap/ImapResponseParser.java
@@ -22,6 +22,7 @@
 import com.android.phone.common.mail.FixedLengthInputStream;
 import com.android.phone.common.mail.MessagingException;
 import com.android.phone.common.mail.PeekableInputStream;
+import com.android.phone.vvm.omtp.VvmLog;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -83,9 +84,7 @@
 
     private static IOException newEOSException() {
         final String message = "End of stream reached";
-        if (Log.isLoggable(TAG, Log.DEBUG)) {
-            Log.d(TAG, message);
-        }
+        VvmLog.d(TAG, message);
         return new IOException(message);
     }
 
@@ -144,9 +143,6 @@
         ImapResponse response = null;
         try {
             response = parseResponse();
-            if (Log.isLoggable(TAG, Log.DEBUG)) {
-                Log.d(TAG, "<<< " + response.toString());
-            }
         } catch (RuntimeException e) {
             // Parser crash -- log network activities.
             onParseError(e);
@@ -183,7 +179,7 @@
             }
         } catch (IOException ignore) {
         }
-        Log.w(TAG, "Exception detected: " + e.getMessage());
+        VvmLog.w(TAG, "Exception detected: " + e.getMessage());
     }
 
     /**
diff --git a/src/com/android/phone/common/mail/store/imap/ImapSimpleString.java b/src/com/android/phone/common/mail/store/imap/ImapSimpleString.java
index 3d5263b..9d65236 100644
--- a/src/com/android/phone/common/mail/store/imap/ImapSimpleString.java
+++ b/src/com/android/phone/common/mail/store/imap/ImapSimpleString.java
@@ -16,7 +16,7 @@
 
 package com.android.phone.common.mail.store.imap;
 
-import android.util.Log;
+import com.android.phone.vvm.omtp.VvmLog;
 
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
@@ -49,7 +49,7 @@
         try {
             return new ByteArrayInputStream(mString.getBytes("US-ASCII"));
         } catch (UnsupportedEncodingException e) {
-            Log.e(TAG, "Unsupported encoding: ", e);
+            VvmLog.e(TAG, "Unsupported encoding: ", e);
         }
         return null;
     }
diff --git a/src/com/android/phone/common/mail/store/imap/ImapString.java b/src/com/android/phone/common/mail/store/imap/ImapString.java
index a33ba24..dd7133c 100644
--- a/src/com/android/phone/common/mail/store/imap/ImapString.java
+++ b/src/com/android/phone/common/mail/store/imap/ImapString.java
@@ -16,7 +16,7 @@
 
 package com.android.phone.common.mail.store.imap;
 
-import android.util.Log;
+import com.android.phone.vvm.omtp.VvmLog;
 
 import java.io.ByteArrayInputStream;
 import java.io.InputStream;
@@ -137,7 +137,7 @@
             mParsedDate = DATE_TIME_FORMAT.parse(getString());
             return true;
         } catch (ParseException e) {
-            Log.w("ImapString", getString() + " can't be parsed as a date.");
+            VvmLog.w("ImapString", getString() + " can't be parsed as a date.");
             return false;
         }
     }
diff --git a/src/com/android/phone/common/mail/utils/LogUtils.java b/src/com/android/phone/common/mail/utils/LogUtils.java
index 711af9b..6bd7be6 100644
--- a/src/com/android/phone/common/mail/utils/LogUtils.java
+++ b/src/com/android/phone/common/mail/utils/LogUtils.java
@@ -20,6 +20,7 @@
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.phone.vvm.omtp.VvmLog;
 
 import java.util.List;
 import java.util.regex.Pattern;
@@ -184,7 +185,7 @@
      */
     public static int v(String tag, String format, Object... args) {
         if (isLoggable(tag, VERBOSE)) {
-            return Log.v(tag, String.format(format, args));
+            return VvmLog.v(tag, String.format(format, args));
         }
         return 0;
     }
@@ -202,7 +203,7 @@
      */
     public static int v(String tag, Throwable tr, String format, Object... args) {
         if (isLoggable(tag, VERBOSE)) {
-            return Log.v(tag, String.format(format, args), tr);
+            return VvmLog.v(tag, String.format(format, args), tr);
         }
         return 0;
     }
@@ -219,7 +220,7 @@
      */
     public static int d(String tag, String format, Object... args) {
         if (isLoggable(tag, DEBUG)) {
-            return Log.d(tag, String.format(format, args));
+            return VvmLog.d(tag, String.format(format, args));
         }
         return 0;
     }
@@ -237,7 +238,7 @@
      */
     public static int d(String tag, Throwable tr, String format, Object... args) {
         if (isLoggable(tag, DEBUG)) {
-            return Log.d(tag, String.format(format, args), tr);
+            return VvmLog.d(tag, String.format(format, args), tr);
         }
         return 0;
     }
@@ -254,7 +255,7 @@
      */
     public static int i(String tag, String format, Object... args) {
         if (isLoggable(tag, INFO)) {
-            return Log.i(tag, String.format(format, args));
+            return VvmLog.i(tag, String.format(format, args));
         }
         return 0;
     }
@@ -272,7 +273,7 @@
      */
     public static int i(String tag, Throwable tr, String format, Object... args) {
         if (isLoggable(tag, INFO)) {
-            return Log.i(tag, String.format(format, args), tr);
+            return VvmLog.i(tag, String.format(format, args), tr);
         }
         return 0;
     }
@@ -289,7 +290,7 @@
      */
     public static int w(String tag, String format, Object... args) {
         if (isLoggable(tag, WARN)) {
-            return Log.w(tag, String.format(format, args));
+            return VvmLog.w(tag, String.format(format, args));
         }
         return 0;
     }
@@ -307,7 +308,7 @@
      */
     public static int w(String tag, Throwable tr, String format, Object... args) {
         if (isLoggable(tag, WARN)) {
-            return Log.w(tag, String.format(format, args), tr);
+            return VvmLog.w(tag, String.format(format, args), tr);
         }
         return 0;
     }
@@ -324,7 +325,7 @@
      */
     public static int e(String tag, String format, Object... args) {
         if (isLoggable(tag, ERROR)) {
-            return Log.e(tag, String.format(format, args));
+            return VvmLog.e(tag, String.format(format, args));
         }
         return 0;
     }
@@ -342,7 +343,7 @@
      */
     public static int e(String tag, Throwable tr, String format, Object... args) {
         if (isLoggable(tag, ERROR)) {
-            return Log.e(tag, String.format(format, args), tr);
+            return VvmLog.e(tag, String.format(format, args), tr);
         }
         return 0;
     }
@@ -362,7 +363,7 @@
      *            additional arguments are ignored.
      */
     public static int wtf(String tag, String format, Object... args) {
-        return Log.wtf(tag, String.format(format, args), new Error());
+        return VvmLog.wtf(tag, String.format(format, args), new Error());
     }
 
     /**
@@ -381,7 +382,7 @@
      *            additional arguments are ignored.
      */
     public static int wtf(String tag, Throwable tr, String format, Object... args) {
-        return Log.wtf(tag, String.format(format, args), tr);
+        return VvmLog.wtf(tag, String.format(format, args), tr);
     }
 
 
diff --git a/src/com/android/phone/settings/VisualVoicemailSettingsUtil.java b/src/com/android/phone/settings/VisualVoicemailSettingsUtil.java
index 62abffd..d7e573e 100644
--- a/src/com/android/phone/settings/VisualVoicemailSettingsUtil.java
+++ b/src/com/android/phone/settings/VisualVoicemailSettingsUtil.java
@@ -22,8 +22,11 @@
 
 import com.android.internal.telephony.Phone;
 import com.android.phone.PhoneUtils;
+import com.android.phone.R;
 import com.android.phone.vvm.omtp.OmtpConstants;
+import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
 import com.android.phone.vvm.omtp.sms.StatusMessage;
+import com.android.phone.vvm.omtp.utils.PhoneAccountHandleConverter;
 
 /**
  * Save visual voicemail login values and whether or not a particular account is enabled in shared
@@ -36,9 +39,6 @@
             "visual_voicemail_";
 
     private static final String IS_ENABLED_KEY = "is_enabled";
-    // If a carrier vvm app is installed, Google visual voicemail is automatically switched off
-    // however, the user can override this setting.
-    private static final String IS_USER_SET = "is_user_set";
     // Record the timestamp of the last full sync so that duplicate syncs can be reduced.
     private static final String LAST_FULL_SYNC_TIMESTAMP = "last_full_sync_timestamp";
     // Constant indicating that there has never been a full sync.
@@ -49,23 +49,13 @@
     private static final long MAX_SYNC_RETRY_INTERVAL_MS = 86400000;   // 24 hours
     private static final long DEFAULT_SYNC_RETRY_INTERVAL_MS = 900000; // 15 minutes
 
-
-    public static void setVisualVoicemailEnabled(Phone phone, boolean isEnabled,
-            boolean isUserSet) {
-        setVisualVoicemailEnabled(phone.getContext(), PhoneUtils.makePstnPhoneAccountHandle(phone),
-                isEnabled, isUserSet);
-    }
-
     public static void setVisualVoicemailEnabled(Context context, PhoneAccountHandle phoneAccount,
-            boolean isEnabled, boolean isUserSet) {
+            boolean isEnabled) {
         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
-        SharedPreferences.Editor editor = prefs.edit();
-        editor.putBoolean(
-                getVisualVoicemailSharedPrefsKey(IS_ENABLED_KEY, phoneAccount), isEnabled);
-        editor.putBoolean(
-                getVisualVoicemailSharedPrefsKey(IS_USER_SET, phoneAccount),
-                isUserSet);
-        editor.commit();
+        prefs.edit()
+                .putBoolean(getVisualVoicemailSharedPrefsKey(IS_ENABLED_KEY, phoneAccount),
+                        isEnabled)
+                .apply();
     }
 
     public static boolean isVisualVoicemailEnabled(Context context,
@@ -73,9 +63,19 @@
         if (phoneAccount == null) {
             return false;
         }
+        if (!context.getResources().getBoolean(R.bool.allow_visual_voicemail)) {
+            return false;
+        }
+
         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
-        return prefs.getBoolean(getVisualVoicemailSharedPrefsKey(IS_ENABLED_KEY, phoneAccount),
-                false);
+        String key = getVisualVoicemailSharedPrefsKey(IS_ENABLED_KEY, phoneAccount);
+        if (prefs.contains(key)) {
+            // isEnableByDefault is a bit expensive, so don't use it as default value of
+            // getBoolean(). The "false" here should never be actually used.
+            return prefs.getBoolean(key, false);
+        }
+        return new OmtpVvmCarrierConfigHelper(context,
+                PhoneAccountHandleConverter.toSubId(phoneAccount)).isEnabledByDefault();
     }
 
     public static boolean isVisualVoicemailEnabled(Phone phone) {
@@ -84,9 +84,10 @@
     }
 
     /**
-     * Differentiate user-enabled/disabled to know whether to ignore automatic enabling and
-     * disabling by the system. This is relevant when a carrier vvm app is installed and the user
-     * manually enables dialer visual voicemail. In that case we would want that setting to persist.
+     * Whether the client enabled status is explicitly set by user or by default(Whether carrier VVM
+     * app is installed). This is used to determine whether to disable the client when the carrier
+     * VVM app is installed. If the carrier VVM app is installed the client should give priority to
+     * it if the settings are not touched.
      */
     public static boolean isVisualVoicemailUserSet(Context context,
             PhoneAccountHandle phoneAccount) {
@@ -94,9 +95,7 @@
             return false;
         }
         SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
-        return prefs.getBoolean(
-                getVisualVoicemailSharedPrefsKey(IS_USER_SET, phoneAccount),
-                false);
+        return prefs.contains(getVisualVoicemailSharedPrefsKey(IS_ENABLED_KEY, phoneAccount));
     }
 
     public static void setVisualVoicemailCredentialsFromStatusMessage(Context context,
diff --git a/src/com/android/phone/settings/VoicemailChangePinDialogPreference.java b/src/com/android/phone/settings/VoicemailChangePinDialogPreference.java
index d960dc4..e4230ff 100644
--- a/src/com/android/phone/settings/VoicemailChangePinDialogPreference.java
+++ b/src/com/android/phone/settings/VoicemailChangePinDialogPreference.java
@@ -35,6 +35,7 @@
 import com.android.phone.common.mail.MessagingException;
 import com.android.phone.vvm.omtp.OmtpConstants;
 import com.android.phone.vvm.omtp.OmtpConstants.ChangePinResult;
+import com.android.phone.vvm.omtp.OmtpEvents;
 import com.android.phone.vvm.omtp.imap.ImapHelper;
 import com.android.phone.vvm.omtp.sync.VvmNetworkRequestCallback;
 
@@ -192,6 +193,7 @@
                     // Wipe the default old PIN so the old PIN input box will be shown to the user
                     // on the next time.
                     setDefaultOldPIN(mContext, mPhoneAccountHandle, null);
+                    helper.handleEvent(OmtpEvents.CONFIG_PIN_SET);
                 }
             } catch (MessagingException e) {
                 finishPinChange();
diff --git a/src/com/android/phone/settings/VoicemailRingtonePreference.java b/src/com/android/phone/settings/VoicemailRingtonePreference.java
index 4ee4e64..bb82d4f 100644
--- a/src/com/android/phone/settings/VoicemailRingtonePreference.java
+++ b/src/com/android/phone/settings/VoicemailRingtonePreference.java
@@ -17,8 +17,13 @@
  * it is created or updated.
  */
 public class VoicemailRingtonePreference extends RingtonePreference {
+    public interface VoicemailRingtoneNameChangeListener {
+        void onVoicemailRingtoneNameChanged(CharSequence name);
+    }
+
     private static final int MSG_UPDATE_VOICEMAIL_RINGTONE_SUMMARY = 1;
 
+    private VoicemailRingtoneNameChangeListener mVoicemailRingtoneNameChangeListener;
     private Runnable mVoicemailRingtoneLookupRunnable;
     private Handler mVoicemailRingtoneLookupComplete;
 
@@ -32,6 +37,10 @@
             public void handleMessage(Message msg) {
                 switch (msg.what) {
                     case MSG_UPDATE_VOICEMAIL_RINGTONE_SUMMARY:
+                        if (mVoicemailRingtoneNameChangeListener != null) {
+                            mVoicemailRingtoneNameChangeListener.onVoicemailRingtoneNameChanged(
+                                    (CharSequence) msg.obj);
+                        }
                         setSummary((CharSequence) msg.obj);
                         break;
                 }
@@ -39,7 +48,7 @@
         };
     }
 
-    public void init(Phone phone) {
+    public void init(Phone phone, CharSequence oldRingtoneName) {
         mPhone = phone;
 
         // Requesting the ringtone will trigger migration if necessary.
@@ -48,6 +57,7 @@
         final Preference preference = this;
         final String preferenceKey =
                 VoicemailNotificationSettingsUtil.getVoicemailRingtoneSharedPrefsKey(mPhone);
+        setSummary(oldRingtoneName);
         mVoicemailRingtoneLookupRunnable = new Runnable() {
             @Override
             public void run() {
@@ -63,6 +73,10 @@
         updateRingtoneName();
     }
 
+    public void setVoicemailRingtoneNameChangeListener(VoicemailRingtoneNameChangeListener l) {
+        mVoicemailRingtoneNameChangeListener = l;
+    }
+
     @Override
     protected Uri onRestoreRingtone() {
         return VoicemailNotificationSettingsUtil.getRingtoneUri(mPhone);
diff --git a/src/com/android/phone/settings/VoicemailSettingsActivity.java b/src/com/android/phone/settings/VoicemailSettingsActivity.java
index fc53f15..b10af6e 100644
--- a/src/com/android/phone/settings/VoicemailSettingsActivity.java
+++ b/src/com/android/phone/settings/VoicemailSettingsActivity.java
@@ -30,6 +30,7 @@
 import android.preference.PreferenceScreen;
 import android.preference.SwitchPreference;
 import android.provider.ContactsContract.CommonDataKinds;
+import android.telecom.PhoneAccountHandle;
 import android.text.BidiFormatter;
 import android.text.TextDirectionHeuristics;
 import android.text.TextUtils;
@@ -58,7 +59,8 @@
         implements DialogInterface.OnClickListener,
                 Preference.OnPreferenceChangeListener,
                 EditPhoneNumberPreference.OnDialogClosedListener,
-                EditPhoneNumberPreference.GetDefaultNumberListener {
+                EditPhoneNumberPreference.GetDefaultNumberListener,
+                VoicemailRingtonePreference.VoicemailRingtoneNameChangeListener {
     private static final String LOG_TAG = VoicemailSettingsActivity.class.getSimpleName();
     private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
 
@@ -185,6 +187,8 @@
     private CallForwardInfo[] mNewFwdSettings;
     private String mNewVMNumber;
 
+    private CharSequence mOldVmRingtoneName = "";
+
     /**
      * Used to indicate that the voicemail preference should be shown.
      */
@@ -252,7 +256,8 @@
 
         mVoicemailNotificationRingtone = (VoicemailRingtonePreference) findPreference(
                 getResources().getString(R.string.voicemail_notification_ringtone_key));
-        mVoicemailNotificationRingtone.init(mPhone);
+        mVoicemailNotificationRingtone.setVoicemailRingtoneNameChangeListener(this);
+        mVoicemailNotificationRingtone.init(mPhone, mOldVmRingtoneName);
 
         mVoicemailNotificationVibrate = (CheckBoxPreference) findPreference(
                 getResources().getString(R.string.voicemail_notification_vibrate_key));
@@ -397,8 +402,10 @@
             VoicemailNotificationSettingsUtil.setVibrationEnabled(
                     mPhone, Boolean.TRUE.equals(objValue));
         } else if (preference.getKey().equals(mVoicemailVisualVoicemail.getKey())) {
-            boolean isEnabled = (Boolean) objValue;
-            VisualVoicemailSettingsUtil.setVisualVoicemailEnabled(mPhone, isEnabled, true);
+            boolean isEnabled = (boolean) objValue;
+            PhoneAccountHandle handle = PhoneUtils.makePstnPhoneAccountHandle(mPhone);
+            VisualVoicemailSettingsUtil
+                    .setVisualVoicemailEnabled(mPhone.getContext(), handle, isEnabled);
             PreferenceScreen prefSet = getPreferenceScreen();
             if (isEnabled) {
                 OmtpVvmSourceManager.getInstance(mPhone.getContext()).addPhoneStateListener(mPhone);
@@ -538,6 +545,11 @@
         super.onActivityResult(requestCode, resultCode, data);
     }
 
+    @Override
+    public void onVoicemailRingtoneNameChanged(CharSequence name) {
+        mOldVmRingtoneName = name;
+    }
+
     /**
      * Simulates user clicking on a passed preference.
      * Usually needed when the preference is a dialog preference and we want to invoke
diff --git a/src/com/android/phone/vvm/omtp/DefaultOmtpEventHandler.java b/src/com/android/phone/vvm/omtp/DefaultOmtpEventHandler.java
index 6816d4c..c49df64 100644
--- a/src/com/android/phone/vvm/omtp/DefaultOmtpEventHandler.java
+++ b/src/com/android/phone/vvm/omtp/DefaultOmtpEventHandler.java
@@ -22,74 +22,75 @@
 
 import com.android.phone.VoicemailStatus;
 import com.android.phone.vvm.omtp.OmtpEvents.Type;
-import com.android.services.telephony.Log;
 
 public class DefaultOmtpEventHandler {
 
     private static final String TAG = "DefErrorCodeHandler";
 
-    public static void handleEvent(Context context, int subId, OmtpEvents event) {
+    public static void handleEvent(Context context, OmtpVvmCarrierConfigHelper config,
+            OmtpEvents event) {
         switch (event.getType()) {
             case Type.CONFIGURATION:
-                handleConfigurationEvent(context, subId, event);
+                handleConfigurationEvent(context, config, event);
                 break;
             case Type.DATA_CHANNEL:
-                handleDataChannelEvent(context, subId, event);
+                handleDataChannelEvent(context, config, event);
                 break;
             case Type.NOTIFICATION_CHANNEL:
-                handleNotificationChannelEvent(context, subId, event);
+                handleNotificationChannelEvent(context, config, event);
                 break;
             case Type.OTHER:
-                handleOtherEvent(context, subId, event);
+                handleOtherEvent(context, config, event);
                 break;
             default:
-                Log.wtf(TAG, "invalid event type " + event.getType() + " for " + event);
+                VvmLog.wtf(TAG, "invalid event type " + event.getType() + " for " + event);
         }
     }
 
-    private static void handleConfigurationEvent(Context context, int subId,
+    private static void handleConfigurationEvent(Context context, OmtpVvmCarrierConfigHelper config,
             OmtpEvents event) {
         switch (event) {
             case CONFIG_REQUEST_STATUS_SUCCESS:
-                VoicemailStatus.edit(context, subId)
+            case CONFIG_PIN_SET:
+                VoicemailStatus.edit(context, config.getSubId())
                         .setConfigurationState(VoicemailContract.Status.CONFIGURATION_STATE_OK)
                         .setNotificationChannelState(Status.NOTIFICATION_CHANNEL_STATE_OK)
                         .apply();
                 break;
             default:
-                Log.wtf(TAG, "invalid configuration event " + event);
+                VvmLog.wtf(TAG, "invalid configuration event " + event);
         }
     }
 
-    private static void handleDataChannelEvent(Context context, int subId,
+    private static void handleDataChannelEvent(Context context, OmtpVvmCarrierConfigHelper config,
             OmtpEvents event) {
         switch (event) {
             case DATA_IMAP_OPERATION_COMPLETED:
-                VoicemailStatus.edit(context, subId)
+                VoicemailStatus.edit(context, config.getSubId())
                         .setDataChannelState(Status.DATA_CHANNEL_STATE_OK)
                         .apply();
                 break;
 
             case DATA_NO_CONNECTION:
-                VoicemailStatus.edit(context, subId)
+                VoicemailStatus.edit(context, config.getSubId())
                         .setDataChannelState(Status.DATA_CHANNEL_STATE_NO_CONNECTION)
                         .apply();
                 break;
 
             case DATA_NO_CONNECTION_CELLULAR_REQUIRED:
-                VoicemailStatus.edit(context, subId)
+                VoicemailStatus.edit(context, config.getSubId())
                         .setDataChannelState(
                                 Status.DATA_CHANNEL_STATE_NO_CONNECTION_CELLULAR_REQUIRED)
                         .apply();
                 break;
             case DATA_INVALID_PORT:
-                VoicemailStatus.edit(context, subId)
+                VoicemailStatus.edit(context, config.getSubId())
                         .setDataChannelState(
                                 VoicemailContract.Status.DATA_CHANNEL_STATE_BAD_CONFIGURATION)
                         .apply();
                 break;
             case DATA_CANNOT_RESOLVE_HOST_ON_NETWORK:
-                VoicemailStatus.edit(context, subId)
+                VoicemailStatus.edit(context, config.getSubId())
                         .setDataChannelState(
                                 VoicemailContract.Status.DATA_CHANNEL_STATE_SERVER_CONNECTION_ERROR)
                         .apply();
@@ -97,13 +98,20 @@
             case DATA_SSL_INVALID_HOST_NAME:
             case DATA_CANNOT_ESTABLISH_SSL_SESSION:
             case DATA_IOE_ON_OPEN:
-                VoicemailStatus.edit(context, subId)
+                VoicemailStatus.edit(context, config.getSubId())
                         .setDataChannelState(
                                 VoicemailContract.Status.DATA_CHANNEL_STATE_COMMUNICATION_ERROR)
                         .apply();
                 break;
             case DATA_BAD_IMAP_CREDENTIAL:
-                VoicemailStatus.edit(context, subId)
+            case DATA_AUTH_UNKNOWN_USER:
+            case DATA_AUTH_UNKNOWN_DEVICE:
+            case DATA_AUTH_INVALID_PASSWORD:
+            case DATA_AUTH_MAILBOX_NOT_INITIALIZED:
+            case DATA_AUTH_SERVICE_NOT_PROVISIONED:
+            case DATA_AUTH_SERVICE_NOT_ACTIVATED:
+            case DATA_AUTH_USER_IS_BLOCKED:
+                VoicemailStatus.edit(context, config.getSubId())
                         .setDataChannelState(
                                 VoicemailContract.Status.DATA_CHANNEL_STATE_BAD_CONFIGURATION)
                         .apply();
@@ -113,40 +121,44 @@
             case DATA_INVALID_INITIAL_SERVER_RESPONSE:
             case DATA_SSL_EXCEPTION:
             case DATA_ALL_SOCKET_CONNECTION_FAILED:
-                VoicemailStatus.edit(context, subId)
+                VoicemailStatus.edit(context, config.getSubId())
                         .setDataChannelState(
                                 VoicemailContract.Status.DATA_CHANNEL_STATE_SERVER_ERROR)
                         .apply();
                 break;
 
             default:
-                Log.wtf(TAG, "invalid data channel event " + event);
+                VvmLog.wtf(TAG, "invalid data channel event " + event);
         }
     }
 
-    private static void handleNotificationChannelEvent(Context context, int subId,
-            OmtpEvents event) {
+    private static void handleNotificationChannelEvent(Context context,
+            OmtpVvmCarrierConfigHelper config, OmtpEvents event) {
         switch (event) {
             case NOTIFICATION_IN_SERVICE:
-                VoicemailStatus.edit(context, subId)
+                VoicemailStatus.edit(context, config.getSubId())
                         .setNotificationChannelState(Status.NOTIFICATION_CHANNEL_STATE_OK)
                         .apply();
                 break;
             case NOTIFICATION_SERVICE_LOST:
-                VoicemailStatus.edit(context, subId)
-                        .setNotificationChannelState(
-                                Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION)
-                        .apply();
+                VoicemailStatus.Editor editor = VoicemailStatus.edit(context, config.getSubId());
+                editor.setNotificationChannelState(Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION);
+                if (config.isCellularDataRequired()) {
+                    editor.setDataChannelState(
+                            Status.DATA_CHANNEL_STATE_NO_CONNECTION_CELLULAR_REQUIRED);
+                }
+                editor.apply();
                 break;
             default:
-                Log.wtf(TAG, "invalid notification channel event " + event);
+                VvmLog.wtf(TAG, "invalid notification channel event " + event);
         }
     }
 
-    private static void handleOtherEvent(Context context, int subId, OmtpEvents event) {
+    private static void handleOtherEvent(Context context, OmtpVvmCarrierConfigHelper config,
+            OmtpEvents event) {
         switch (event) {
             case OTHER_SOURCE_REMOVED:
-                VoicemailStatus.edit(context, subId)
+                VoicemailStatus.edit(context, config.getSubId())
                         .setConfigurationState(Status.CONFIGURATION_STATE_NOT_CONFIGURED)
                         .setNotificationChannelState(
                                 Status.NOTIFICATION_CHANNEL_STATE_NO_CONNECTION)
@@ -154,7 +166,7 @@
                         .apply();
                 break;
             default:
-                Log.wtf(TAG, "invalid other event " + event);
+                VvmLog.wtf(TAG, "invalid other event " + event);
         }
     }
 }
diff --git a/src/com/android/phone/vvm/omtp/LocalLogHelper.java b/src/com/android/phone/vvm/omtp/LocalLogHelper.java
deleted file mode 100644
index a3de74f..0000000
--- a/src/com/android/phone/vvm/omtp/LocalLogHelper.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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 com.android.internal.telephony.PhoneFactory;
-
-/**
- * Helper methods for adding to Telephony local logs.
- */
-public class LocalLogHelper {
-    public static final String KEY = "OmtpVvm";
-    private static final int MAX_OMTP_VVM_LOGS = 20;
-
-    public static void log(String tag, String log) {
-        try {
-            PhoneFactory.addLocalLog(KEY, MAX_OMTP_VVM_LOGS);
-        } catch (IllegalArgumentException e){
-        } finally {
-            PhoneFactory.localLog(KEY, tag + ": " + log);
-        }
-    }
-}
diff --git a/src/com/android/phone/vvm/omtp/OmtpBootCompletedReceiver.java b/src/com/android/phone/vvm/omtp/OmtpBootCompletedReceiver.java
deleted file mode 100644
index a2b85f7..0000000
--- a/src/com/android/phone/vvm/omtp/OmtpBootCompletedReceiver.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.phone.vvm.omtp;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
-import android.util.ArraySet;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-
-import java.util.Set;
-
-/**
- * Stores subscription ID of SIMs while the device is locked to process them after the device is
- * unlocked. This class is only intended to be used within {@link SimChangeReceiver}. subId is used
- * for Visual voicemail activation/deactivation, which need to be done when the device is unlocked.
- * But the enumeration of subIds happen on boot, when the device could be locked. This class is used
- * to defer all activation/deactivation until the device is unlocked.
- *
- * The subIds are stored in device encrypted {@link SharedPreferences} (readable/writable even
- * locked). after the device is unlocked the list is read and deleted.
- */
-public class OmtpBootCompletedReceiver extends BroadcastReceiver {
-
-    private static final String TAG = "OmtpBootCompletedRcvr";
-
-    private static final String DEFERRED_SUBID_LIST_KEY = "deferred_sub_id_key";
-
-    @VisibleForTesting
-    interface SubIdProcessor{
-        void process(Context context,int subId);
-    }
-
-    private SubIdProcessor mSubIdProcessor = new SubIdProcessor() {
-        @Override
-        public void process(Context context, int subId) {
-            SimChangeReceiver.processSubId(context,subId);
-        }
-    };
-
-    /**
-     * Write the subId to the the list.
-     */
-    public static void addDeferredSubId(Context context, int subId) {
-        SharedPreferences sharedPreferences = getSubIdSharedPreference(context);
-        Set<String> subIds =
-                new ArraySet<>(sharedPreferences.getStringSet(DEFERRED_SUBID_LIST_KEY, null));
-        subIds.add(String.valueOf(subId));
-        sharedPreferences.edit().putStringSet(DEFERRED_SUBID_LIST_KEY, subIds).apply();
-    }
-
-    @Override
-    public void onReceive(Context context, Intent intent) {
-        // Listens to android.intent.action.BOOT_COMPLETED
-        if(!intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
-            return;
-        }
-
-        Log.v(TAG, "processing deferred subId list");
-        Set<Integer> subIds = readAndDeleteSubIds(context);
-        for (Integer subId : subIds) {
-            Log.v(TAG, "processing subId " + subId);
-            mSubIdProcessor.process(context, subId);
-        }
-    }
-
-    /**
-     * Read all subId from the list to a unique integer set, and delete the preference.
-     */
-    private static Set<Integer> readAndDeleteSubIds(Context context) {
-        SharedPreferences sharedPreferences = getSubIdSharedPreference(context);
-        Set<String> subIdStrings = sharedPreferences.getStringSet(DEFERRED_SUBID_LIST_KEY, null);
-        Set<Integer> subIds = new ArraySet<>();
-        if(subIdStrings == null) {
-            return subIds;
-        }
-        for(String string : subIdStrings){
-            subIds.add(Integer.valueOf(string));
-        }
-        getSubIdSharedPreference(context).edit().remove(DEFERRED_SUBID_LIST_KEY).apply();
-        return subIds;
-    }
-
-    @VisibleForTesting
-    void setSubIdProcessorForTest(SubIdProcessor processor){
-        mSubIdProcessor = processor;
-    }
-
-    private static SharedPreferences getSubIdSharedPreference(Context context) {
-        return PreferenceManager
-                .getDefaultSharedPreferences(context.createDeviceProtectedStorageContext());
-    }
-}
diff --git a/src/com/android/phone/vvm/omtp/OmtpEvents.java b/src/com/android/phone/vvm/omtp/OmtpEvents.java
index 6de692e..f42db72 100644
--- a/src/com/android/phone/vvm/omtp/OmtpEvents.java
+++ b/src/com/android/phone/vvm/omtp/OmtpEvents.java
@@ -31,43 +31,55 @@
     // Configuration State
     CONFIG_REQUEST_STATUS_SUCCESS(Type.CONFIGURATION, true),
 
+    CONFIG_PIN_SET(Type.CONFIGURATION, true),
+    // The voicemail PIN is replaced with a generated PIN, user should change it.
+    CONFIG_DEFAULT_PIN_REPLACED(Type.CONFIGURATION, true),
+
     // Data channel State
 
     // Successfully downloaded/uploaded data from the server, which means the data channel is clear.
     DATA_IMAP_OPERATION_COMPLETED(Type.DATA_CHANNEL, true),
-
     // The port provided in the STATUS SMS is invalid.
-    DATA_INVALID_PORT(Type.DATA_CHANNEL, false),
+    DATA_INVALID_PORT(Type.DATA_CHANNEL),
     // No connection to the internet, and the carrier requires cellular data
-    DATA_NO_CONNECTION_CELLULAR_REQUIRED(Type.DATA_CHANNEL, false),
+    DATA_NO_CONNECTION_CELLULAR_REQUIRED(Type.DATA_CHANNEL),
     // No connection to the internet.
-    DATA_NO_CONNECTION(Type.DATA_CHANNEL, false),
+    DATA_NO_CONNECTION(Type.DATA_CHANNEL),
     // Address lookup for the server hostname failed. DNS error?
-    DATA_CANNOT_RESOLVE_HOST_ON_NETWORK(Type.DATA_CHANNEL, false),
+    DATA_CANNOT_RESOLVE_HOST_ON_NETWORK(Type.DATA_CHANNEL),
     // All destination address that resolves to the server hostname are rejected or timed out
-    DATA_ALL_SOCKET_CONNECTION_FAILED(Type.DATA_CHANNEL, false),
+    DATA_ALL_SOCKET_CONNECTION_FAILED(Type.DATA_CHANNEL),
     // Failed to establish SSL with the server, either with a direct SSL connection or by
     // STARTTLS command
-    DATA_CANNOT_ESTABLISH_SSL_SESSION(Type.DATA_CHANNEL, false),
+    DATA_CANNOT_ESTABLISH_SSL_SESSION(Type.DATA_CHANNEL),
     // Identity of the server cannot be verified.
-    DATA_SSL_INVALID_HOST_NAME(Type.DATA_CHANNEL, false),
+    DATA_SSL_INVALID_HOST_NAME(Type.DATA_CHANNEL),
     // The server rejected our username/password
-    DATA_BAD_IMAP_CREDENTIAL(Type.DATA_CHANNEL, false),
+    DATA_BAD_IMAP_CREDENTIAL(Type.DATA_CHANNEL),
+
+    DATA_AUTH_UNKNOWN_USER(Type.DATA_CHANNEL),
+    DATA_AUTH_UNKNOWN_DEVICE(Type.DATA_CHANNEL),
+    DATA_AUTH_INVALID_PASSWORD(Type.DATA_CHANNEL),
+    DATA_AUTH_MAILBOX_NOT_INITIALIZED(Type.DATA_CHANNEL),
+    DATA_AUTH_SERVICE_NOT_PROVISIONED(Type.DATA_CHANNEL),
+    DATA_AUTH_SERVICE_NOT_ACTIVATED(Type.DATA_CHANNEL),
+    DATA_AUTH_USER_IS_BLOCKED(Type.DATA_CHANNEL),
+
     // A command to the server didn't result with an "OK" or continuation request
-    DATA_REJECTED_SERVER_RESPONSE(Type.DATA_CHANNEL, false),
+    DATA_REJECTED_SERVER_RESPONSE(Type.DATA_CHANNEL),
     // The server did not greet us with a "OK", possibly not a IMAP server.
-    DATA_INVALID_INITIAL_SERVER_RESPONSE(Type.DATA_CHANNEL, false),
+    DATA_INVALID_INITIAL_SERVER_RESPONSE(Type.DATA_CHANNEL),
     // An IOException occurred while trying to open an ImapConnection
     // TODO: reduce scope
-    DATA_IOE_ON_OPEN(Type.DATA_CHANNEL, false),
+    DATA_IOE_ON_OPEN(Type.DATA_CHANNEL),
     // The SELECT command on a mailbox is rejected
-    DATA_MAILBOX_OPEN_FAILED(Type.DATA_CHANNEL, false),
+    DATA_MAILBOX_OPEN_FAILED(Type.DATA_CHANNEL),
     // An IOException has occurred
     // TODO: reduce scope
-    DATA_GENERIC_IMAP_IOE(Type.DATA_CHANNEL, false),
+    DATA_GENERIC_IMAP_IOE(Type.DATA_CHANNEL),
     // An SslException has occurred while opening an ImapConnection
     // TODO: reduce scope
-    DATA_SSL_EXCEPTION(Type.DATA_CHANNEL, false),
+    DATA_SSL_EXCEPTION(Type.DATA_CHANNEL),
 
     // Notification Channel
 
@@ -78,8 +90,20 @@
 
 
     // Other
-    OTHER_SOURCE_REMOVED(Type.OTHER, false);
+    OTHER_SOURCE_REMOVED(Type.OTHER, false),
 
+    // VVM3
+    VVM3_NEW_USER_SETUP_FAILED,
+    // Table 4. client internal error handling
+    VVM3_VMG_DNS_FAILURE,
+    VVM3_SPG_DNS_FAILURE,
+    VVM3_VMG_CONNECTION_FAILED,
+    VVM3_SPG_CONNECTION_FAILED,
+    VVM3_VMG_TIMEOUT,
+    VVM3_STATUS_SMS_TIMEOUT,
+
+    VVM3_SUBSCRIBER_PROVISIONED,
+    VVM3_SUBSCRIBER_BLOCKED;
 
     public static class Type {
 
@@ -103,6 +127,15 @@
         mIsSuccess = isSuccess;
     }
 
+    OmtpEvents(int type) {
+        mType = type;
+        mIsSuccess = false;
+    }
+
+    OmtpEvents() {
+        mType = Type.OTHER;
+        mIsSuccess = false;
+    }
 
     @Type.Values
     public int getType() {
diff --git a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
index 798f5da..b570744 100644
--- a/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
+++ b/src/com/android/phone/vvm/omtp/OmtpVvmCarrierConfigHelper.java
@@ -27,7 +27,6 @@
 import android.telephony.VisualVoicemailSmsFilterSettings;
 import android.text.TextUtils;
 import android.util.ArraySet;
-import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.phone.VoicemailStatus;
@@ -74,6 +73,12 @@
             "vvm_ssl_port_number_int";
 
     /**
+     * @see #isLegacyModeEnabled()
+     */
+    static final String KEY_VVM_LEGACY_MODE_ENABLED_BOOL =
+            "vvm_legacy_mode_enabled_bool";
+
+    /**
      * Ban a capability reported by the server from being used. The array of string should be a
      * subset of the capabilities returned IMAP CAPABILITY command.
      *
@@ -99,7 +104,7 @@
         TelephonyManager telephonyManager =
                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
         mTelephonyConfig = new TelephonyVvmConfigManager(context.getResources())
-                .getConfig(telephonyManager.getNetworkOperator(subId));
+                .getConfig(telephonyManager.getSimOperator(subId));
 
         mVvmType = getVvmType();
         mProtocol = VisualVoicemailProtocolFactory.create(mVvmType);
@@ -174,6 +179,10 @@
      * so by checking if the carrier's voicemail app is installed.
      */
     public boolean isEnabledByDefault() {
+        if (!isValid()) {
+            return false;
+        }
+
         Set<String> carrierPackages = getCarrierVvmPackageNames();
         if (carrierPackages == null) {
             return true;
@@ -259,24 +268,46 @@
         return "//VVM";
     }
 
+    /**
+     * Should legacy mode be used when the OMTP VVM client is disabled?
+     *
+     * <p>Legacy mode is a mode that on the carrier side visual voicemail is still activated, but on
+     * the client side all network operations are disabled. SMSs are still monitored so a new
+     * message SYNC SMS will be translated to show a message waiting indicator, like traditional
+     * voicemails.
+     *
+     * <p>This is for carriers that does not support VVM deactivation so voicemail can continue to
+     * function without the data cost.
+     */
+    public boolean isLegacyModeEnabled() {
+        return (boolean) getValue(KEY_VVM_LEGACY_MODE_ENABLED_BOOL, false);
+    }
+
     public void startActivation() {
         VoicemailStatus.edit(mContext, mSubId)
                 .setType(getVvmType())
                 .apply();
 
-        TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
-        telephonyManager.enableVisualVoicemailSmsFilter(mSubId,
-                new VisualVoicemailSmsFilterSettings.Builder().setClientPrefix(getClientPrefix())
-                        .build());
+        activateSmsFilter();
 
         if (mProtocol != null) {
             mProtocol.startActivation(this);
         }
     }
 
+    public void activateSmsFilter() {
+        TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
+        telephonyManager.enableVisualVoicemailSmsFilter(mSubId,
+                new VisualVoicemailSmsFilterSettings.Builder().setClientPrefix(getClientPrefix())
+                        .build());
+    }
+
     public void startDeactivation() {
-        mContext.getSystemService(TelephonyManager.class)
-                .disableVisualVoicemailSmsFilter(mSubId);
+        if (!isLegacyModeEnabled()) {
+            // SMS should still be filtered in legacy mode
+            mContext.getSystemService(TelephonyManager.class)
+                    .disableVisualVoicemailSmsFilter(mSubId);
+        }
         if (mProtocol != null) {
             mProtocol.startDeactivation(this);
         }
@@ -295,29 +326,48 @@
     }
 
     public void handleEvent(OmtpEvents event) {
+        VvmLog.i(TAG, "OmtpEvent:" + event);
         if (mProtocol != null) {
-            mProtocol.handleEvent(mContext, mSubId, event);
+            mProtocol.handleEvent(mContext, this, event);
         }
     }
 
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder("OmtpVvmCarrierConfigHelper [");
+        builder.append("subId: ").append(getSubId())
+                .append(", carrierConfig: ").append(mCarrierConfig != null)
+                .append(", telephonyConfig: ").append(mTelephonyConfig != null)
+                .append(", type: ").append(getVvmType())
+                .append(", destinationNumber: ").append(getDestinationNumber())
+                .append(", applicationPort: ").append(getApplicationPort())
+                .append(", sslPort: ").append(getSslPort())
+                .append(", isEnabledByDefault: ").append(isEnabledByDefault())
+                .append(", isCellularDataRequired: ").append(isCellularDataRequired())
+                .append(", isPrefetchEnabled: ").append(isPrefetchEnabled())
+                .append(", isLegacyModeEnabled: ").append(isLegacyModeEnabled())
+                .append("]");
+        return builder.toString();
+    }
+
     @Nullable
     private PersistableBundle getCarrierConfig() {
         if (!SubscriptionManager.isValidSubscriptionId(mSubId)) {
-            Log.w(TAG, "Invalid subscriptionId or subscriptionId not provided in intent.");
+            VvmLog
+                    .w(TAG, "Invalid subscriptionId or subscriptionId not provided in intent.");
             return null;
         }
 
         CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
                 mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
         if (carrierConfigManager == null) {
-            Log.w(TAG, "No carrier config service found.");
+            VvmLog.w(TAG, "No carrier config service found.");
             return null;
         }
 
         PersistableBundle config = carrierConfigManager.getConfigForSubId(mSubId);
 
         if (TextUtils.isEmpty(config.getString(CarrierConfigManager.KEY_VVM_TYPE_STRING))) {
-            Log.w(TAG, "Carrier config missing VVM type, ignoring.");
             return null;
         }
         return config;
diff --git a/src/com/android/phone/vvm/omtp/SimChangeReceiver.java b/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
index 01d6bf9..f22711a 100644
--- a/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
+++ b/src/com/android/phone/vvm/omtp/SimChangeReceiver.java
@@ -18,17 +18,18 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
-import android.os.UserHandle;
+import android.content.pm.IPackageManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.UserManager;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
-import android.util.Log;
+import android.telephony.TelephonyManager;
 
 import com.android.internal.telephony.IccCardConstants;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.telephony.TelephonyIntents;
-import com.android.phone.R;
 import com.android.phone.settings.VisualVoicemailSettingsUtil;
 import com.android.phone.vvm.omtp.sync.OmtpVvmSourceManager;
 import com.android.phone.vvm.omtp.utils.PhoneAccountHandleConverter;
@@ -43,18 +44,13 @@
  */
 public class SimChangeReceiver extends BroadcastReceiver {
 
-    private static final String TAG = "SimChangeReceiver";
+    private static final String TAG = "VvmSimChangeReceiver";
 
     @Override
     public void onReceive(Context context, Intent intent) {
-        if (UserHandle.myUserId() != UserHandle.USER_SYSTEM) {
-            Log.v(TAG, "Received broadcast for user that is not system.");
-            return;
-        }
-
         final String action = intent.getAction();
         if (action == null) {
-            Log.w(TAG, "Null action for intent.");
+            VvmLog.w(TAG, "Null action for intent.");
             return;
         }
 
@@ -62,7 +58,7 @@
             case TelephonyIntents.ACTION_SIM_STATE_CHANGED:
                 if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(
                         intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE))) {
-                    Log.i(TAG, "Sim removed, removing inactive accounts");
+                    VvmLog.i(TAG, "Sim removed, removing inactive accounts");
                     OmtpVvmSourceManager.getInstance(context).removeInactiveSources();
                 }
                 break;
@@ -71,14 +67,16 @@
                         SubscriptionManager.INVALID_SUBSCRIPTION_ID);
 
                 if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
-                    Log.i(TAG, "Received SIM change for invalid subscription id.");
+                    VvmLog.i(TAG, "Received SIM change for invalid subscription id.");
                     return;
                 }
-
-                if (!UserManager.get(context).isUserUnlocked()) {
-                    OmtpBootCompletedReceiver.addDeferredSubId(context, subId);
-                } else {
+                VvmLog.d(TAG, "Carrier config changed");
+                if (UserManager.get(context).isUserUnlocked() && !isCryptKeeperMode()) {
                     processSubId(context, subId);
+                } else {
+                    VvmLog.d(TAG, "User locked, activation request delayed until unlock");
+                    // After the device is unlocked, VvmBootCompletedReceiver will iterate through
+                    // all call capable subIds, nothing need to be done here.
                 }
                 break;
         }
@@ -90,26 +88,9 @@
         if (carrierConfigHelper.isValid()) {
             PhoneAccountHandle phoneAccount = PhoneAccountHandleConverter.fromSubId(subId);
 
-            boolean isUserSet = VisualVoicemailSettingsUtil.isVisualVoicemailUserSet(
-                    context, phoneAccount);
-            boolean isEnabledInSettings =
-                    VisualVoicemailSettingsUtil.isVisualVoicemailEnabled(context,
-                            phoneAccount);
-            boolean isSupported =
-                    context.getResources().getBoolean(R.bool.allow_visual_voicemail);
-            boolean isEnabled = isSupported && (isUserSet ? isEnabledInSettings :
-                    carrierConfigHelper.isEnabledByDefault());
-
-            if (!isUserSet) {
-                // Preserve the previous setting for "isVisualVoicemailEnabled" if it is
-                // set by the user, otherwise, set this value for the first time.
-                VisualVoicemailSettingsUtil.setVisualVoicemailEnabled(context, phoneAccount,
-                        isEnabled, /** isUserSet */false);
-            }
-
-            if (isEnabled) {
-                LocalLogHelper.log(TAG, "Sim state or carrier config changed: requesting"
-                        + " activation for " + phoneAccount.getId());
+            if (VisualVoicemailSettingsUtil.isVisualVoicemailEnabled(context, phoneAccount)) {
+                VvmLog.i(TAG, "Sim state or carrier config changed: requesting"
+                        + " activation for " + subId);
 
                 // Add a phone state listener so that changes to the communication channels
                 // can be recorded.
@@ -117,11 +98,35 @@
                         phoneAccount);
                 carrierConfigHelper.startActivation();
             } else {
+                if (carrierConfigHelper.isLegacyModeEnabled()) {
+                    // SMS still need to be filtered under legacy mode.
+                    VvmLog.i(TAG, "activating SMS filter for legacy mode");
+                    carrierConfigHelper.activateSmsFilter();
+                }
                 // It may be that the source was not registered to begin with but we want
                 // to run through the steps to remove the source just in case.
                 OmtpVvmSourceManager.getInstance(context).removeSource(phoneAccount);
-                Log.v(TAG, "Sim change for disabled account.");
+                VvmLog.v(TAG, "Sim change for disabled account.");
             }
+        } else {
+            String mccMnc = context.getSystemService(TelephonyManager.class).getSimOperator(subId);
+            VvmLog.d(TAG,
+                    "visual voicemail not supported for carrier " + mccMnc + " on subId " + subId);
         }
     }
+
+    /**
+     * CryptKeeper mode is the pre-file based encryption locked state, when the user has selected
+     * "Require password to boot" and the device hasn't been unlocked yet during a reboot. {@link
+     * UserManager#isUserUnlocked()} will still return true in this mode, but storage in /data and
+     * all content providers will not be available(including SharedPreference).
+     */
+    private static boolean isCryptKeeperMode() {
+        try {
+            return IPackageManager.Stub.asInterface(ServiceManager.getService("package")).
+                    isOnlyCoreApps();
+        } catch (RemoteException e) {
+        }
+        return false;
+    }
 }
\ No newline at end of file
diff --git a/src/com/android/phone/vvm/omtp/VvmBootCompletedReceiver.java b/src/com/android/phone/vvm/omtp/VvmBootCompletedReceiver.java
new file mode 100644
index 0000000..fe1f4cb
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/VvmBootCompletedReceiver.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.phone.vvm.omtp;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+
+import com.android.phone.vvm.omtp.utils.PhoneAccountHandleConverter;
+
+/**
+ * Upon boot iterate through all callable phone account to activate visual voicemail. This happens
+ * after the device has been unlocked. {@link android.telephony.CarrierConfigManager#
+ * ACTION_CARRIER_CONFIG_CHANGED} can also trigger activation upon boot but it can happen before the
+ * device is unlocked and visual voicemail will not be activated.
+ *
+ * <p>TODO: An additional duplicated activation request will be sent as a result of this receiver,
+ * but similar issues is already covered in b/28730056 and a scheduling system should be used to
+ * resolve this.
+ */
+public class VvmBootCompletedReceiver extends BroadcastReceiver {
+
+    private static final String TAG = "VvmBootCompletedRcvr";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        // Listens to android.intent.action.BOOT_COMPLETED
+        if (!intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
+            return;
+        }
+
+        VvmLog.v(TAG, "processing subId list");
+        for (PhoneAccountHandle handle : TelecomManager.from(context)
+                .getCallCapablePhoneAccounts()) {
+            int subId = PhoneAccountHandleConverter.toSubId(handle);
+            VvmLog.v(TAG, "processing subId " + subId);
+            SimChangeReceiver.processSubId(context, subId);
+        }
+    }
+}
diff --git a/src/com/android/phone/vvm/omtp/VvmLog.java b/src/com/android/phone/vvm/omtp/VvmLog.java
new file mode 100644
index 0000000..82d42af
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/VvmLog.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;
+
+import android.util.LocalLog;
+import android.util.Log;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * Helper methods for adding to OMTP visual voicemail local logs.
+ */
+public class VvmLog {
+
+    private static final int MAX_OMTP_VVM_LOGS = 100;
+
+    private static final LocalLog sLocalLog = new LocalLog(MAX_OMTP_VVM_LOGS);
+
+    public static void log(String tag, String log) {
+        sLocalLog.log(tag + ": " + log);
+    }
+
+    public static void dump(FileDescriptor fd, PrintWriter printwriter, String[] args) {
+        IndentingPrintWriter indentingPrintWriter = new IndentingPrintWriter(printwriter, "  ");
+        indentingPrintWriter.increaseIndent();
+        sLocalLog.dump(fd, indentingPrintWriter, args);
+        indentingPrintWriter.decreaseIndent();
+    }
+
+    public static int e(String tag, String log) {
+        log(tag, log);
+        return Log.e(tag, log);
+    }
+
+    public static int e(String tag, String log, Throwable e) {
+        log(tag, log + " " + e);
+        return Log.e(tag, log, e);
+    }
+
+    public static int w(String tag, String log) {
+        log(tag, log);
+        return Log.w(tag, log);
+    }
+
+    public static int w(String tag, String log, Throwable e) {
+        log(tag, log + " " + e);
+        return Log.w(tag, log, e);
+    }
+
+    public static int i(String tag, String log) {
+        log(tag, log);
+        return Log.i(tag, log);
+    }
+
+    public static int i(String tag, String log, Throwable e) {
+        log(tag, log + " " + e);
+        return Log.i(tag, log, e);
+    }
+
+    public static int d(String tag, String log) {
+        log(tag, log);
+        return Log.d(tag, log);
+    }
+
+    public static int d(String tag, String log, Throwable e) {
+        log(tag, log + " " + e);
+        return Log.d(tag, log, e);
+    }
+
+    public static int v(String tag, String log) {
+        log(tag, log);
+        return Log.v(tag, log);
+    }
+
+    public static int v(String tag, String log, Throwable e) {
+        log(tag, log + " " + e);
+        return Log.v(tag, log, e);
+    }
+
+    public static int wtf(String tag, String log) {
+        log(tag, log);
+        return Log.wtf(tag, log);
+    }
+
+    public static int wtf(String tag, String log, Throwable e) {
+        log(tag, log + " " + e);
+        return Log.wtf(tag, log, e);
+    }
+}
diff --git a/src/com/android/phone/vvm/omtp/VvmPackageInstallReceiver.java b/src/com/android/phone/vvm/omtp/VvmPackageInstallReceiver.java
index 8d438ef..8a0495b 100644
--- a/src/com/android/phone/vvm/omtp/VvmPackageInstallReceiver.java
+++ b/src/com/android/phone/vvm/omtp/VvmPackageInstallReceiver.java
@@ -31,6 +31,9 @@
  * enabled dialer vvm sources.
  */
 public class VvmPackageInstallReceiver extends BroadcastReceiver {
+
+    private static final String TAG = "VvmPkgInstallReceiver";
+
     @Override
     public void onReceive(Context context, Intent intent) {
         if (intent.getData() == null) {
@@ -56,8 +59,10 @@
                 continue;
             }
             if (carrierConfigHelper.getCarrierVvmPackageNames().contains(packageName)) {
-                VisualVoicemailSettingsUtil.setVisualVoicemailEnabled(
-                        context, phoneAccount, false, false);
+                // Force deactivate the client. The user can re-enable it in the settings.
+                // There are no need to update the settings for deactivation. At this point, if the
+                // default value is used it should be false because a carrier package is present.
+                VvmLog.i(TAG, "Carrier VVM package installed, disabling system VVM client");
                 OmtpVvmSourceManager.getInstance(context).removeSource(phoneAccount);
                 carrierConfigHelper.startDeactivation();
             }
diff --git a/src/com/android/phone/vvm/omtp/VvmPhoneStateListener.java b/src/com/android/phone/vvm/omtp/VvmPhoneStateListener.java
index 3438de2..64b37c6 100644
--- a/src/com/android/phone/vvm/omtp/VvmPhoneStateListener.java
+++ b/src/com/android/phone/vvm/omtp/VvmPhoneStateListener.java
@@ -20,7 +20,6 @@
 import android.telecom.PhoneAccountHandle;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
-import android.util.Log;
 
 import com.android.phone.PhoneGlobals;
 import com.android.phone.PhoneUtils;
@@ -65,16 +64,16 @@
                     new VoicemailStatusQueryHelper(mContext);
             if (voicemailStatusQueryHelper.isVoicemailSourceConfigured(mPhoneAccount)) {
                 if (!voicemailStatusQueryHelper.isNotificationsChannelActive(mPhoneAccount)) {
-                    Log.v(TAG, "Notifications channel is active for " + mPhoneAccount.getId());
+                    VvmLog
+                            .v(TAG, "Notifications channel is active for " + subId);
                     helper.handleEvent(OmtpEvents.NOTIFICATION_IN_SERVICE);
                     PhoneGlobals.getInstance().clearMwiIndicator(subId);
                 }
             }
 
             if (OmtpVvmSourceManager.getInstance(mContext).isVvmSourceRegistered(mPhoneAccount)) {
-                Log.v(TAG, "Signal returned: requesting resync for " + mPhoneAccount.getId());
-                LocalLogHelper.log(TAG,
-                        "Signal returned: requesting resync for " + mPhoneAccount.getId());
+                VvmLog
+                        .v(TAG, "Signal returned: requesting resync for " + subId);
                 // If the source is already registered, run a full sync in case something was missed
                 // while signal was down.
                 Intent serviceIntent = OmtpVvmSyncService.getSyncIntent(
@@ -82,16 +81,15 @@
                         true /* firstAttempt */);
                 mContext.startService(serviceIntent);
             } else {
-                Log.v(TAG, "Signal returned: reattempting activation for " + mPhoneAccount.getId());
-                LocalLogHelper.log(TAG,
-                        "Signal returned: reattempting activation for " + mPhoneAccount.getId());
+                VvmLog.v(TAG,
+                        "Signal returned: reattempting activation for " + subId);
                 // Otherwise initiate an activation because this means that an OMTP source was
                 // recognized but either the activation text was not successfully sent or a response
                 // was not received.
                 helper.startActivation();
             }
         } else {
-            Log.v(TAG, "Notifications channel is inactive for " + mPhoneAccount.getId());
+            VvmLog.v(TAG, "Notifications channel is inactive for " + subId);
             mContext.stopService(OmtpVvmSyncService.getSyncIntent(
                     mContext, OmtpVvmSyncService.SYNC_FULL_SYNC, mPhoneAccount,
                     true /* firstAttempt */));
diff --git a/src/com/android/phone/vvm/omtp/fetch/FetchVoicemailReceiver.java b/src/com/android/phone/vvm/omtp/fetch/FetchVoicemailReceiver.java
index 0095f53..fe3911c 100644
--- a/src/com/android/phone/vvm/omtp/fetch/FetchVoicemailReceiver.java
+++ b/src/com/android/phone/vvm/omtp/fetch/FetchVoicemailReceiver.java
@@ -30,10 +30,10 @@
 import android.telecom.PhoneAccountHandle;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
-import android.util.Log;
 
 import com.android.phone.PhoneUtils;
 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.sync.OmtpVvmSourceManager;
 import com.android.phone.vvm.omtp.sync.VvmNetworkRequestCallback;
@@ -45,7 +45,7 @@
 
     private static final String TAG = "FetchVoicemailReceiver";
 
-    final static String[] PROJECTION = new String[] {
+    final static String[] PROJECTION = new String[]{
             Voicemails.SOURCE_DATA,      // 0
             Voicemails.PHONE_ACCOUNT_ID, // 1
             Voicemails.PHONE_ACCOUNT_COMPONENT_NAME, // 2
@@ -74,23 +74,28 @@
     @Override
     public void onReceive(final Context context, Intent intent) {
         if (VoicemailContract.ACTION_FETCH_VOICEMAIL.equals(intent.getAction())) {
+            VvmLog.i(TAG, "ACTION_FETCH_VOICEMAIL received");
             mContext = context;
             mContentResolver = context.getContentResolver();
             mUri = intent.getData();
 
             if (mUri == null) {
-                Log.w(TAG, VoicemailContract.ACTION_FETCH_VOICEMAIL + " intent sent with no data");
+                VvmLog.w(TAG,
+                        VoicemailContract.ACTION_FETCH_VOICEMAIL + " intent sent with no data");
                 return;
             }
 
             if (!context.getPackageName().equals(
                     mUri.getQueryParameter(VoicemailContract.PARAM_KEY_SOURCE_PACKAGE))) {
                 // Ignore if the fetch request is for a voicemail not from this package.
+                VvmLog.e(TAG,
+                        "ACTION_FETCH_VOICEMAIL from foreign pacakge " + context.getPackageName());
                 return;
             }
 
             Cursor cursor = mContentResolver.query(mUri, PROJECTION, null, null, null);
             if (cursor == null) {
+                VvmLog.i(TAG, "ACTION_FETCH_VOICEMAIL query returned null");
                 return;
             }
             try {
@@ -103,7 +108,7 @@
                         accountId = telephonyManager.getSimSerialNumber();
 
                         if (TextUtils.isEmpty(accountId)) {
-                            Log.e(TAG, "Account null and no default sim found.");
+                            VvmLog.e(TAG, "Account null and no default sim found.");
                             return;
                         }
                     }
@@ -114,14 +119,14 @@
                             cursor.getString(PHONE_ACCOUNT_ID));
                     if (!OmtpVvmSourceManager.getInstance(context)
                             .isVvmSourceRegistered(mPhoneAccount)) {
-                        Log.w(TAG, "Account not registered - cannot retrieve message.");
+                        VvmLog.w(TAG, "Account not registered - cannot retrieve message.");
                         return;
                     }
 
                     int subId = PhoneUtils.getSubIdForPhoneAccountHandle(mPhoneAccount);
                     OmtpVvmCarrierConfigHelper carrierConfigHelper =
                             new OmtpVvmCarrierConfigHelper(context, subId);
-
+                    VvmLog.i(TAG, "Requesting network to fetch voicemail");
                     mNetworkCallback = new fetchVoicemailNetworkRequestCallback(context,
                             mPhoneAccount);
                     mNetworkCallback.requestNetwork();
@@ -153,15 +158,17 @@
             public void run() {
                 try {
                     while (mRetryCount > 0) {
+                        VvmLog.i(TAG, "fetching voicemail, retry count=" + mRetryCount);
                         ImapHelper imapHelper = new ImapHelper(mContext, mPhoneAccount, network);
                         if (!imapHelper.isSuccessfullyInitialized()) {
-                            Log.w(TAG, "Can't retrieve Imap credentials.");
+                            VvmLog.w(TAG, "Can't retrieve Imap credentials.");
                             return;
                         }
 
                         boolean success = imapHelper.fetchVoicemailPayload(
                                 new VoicemailFetchedCallback(mContext, mUri), mUid);
                         if (!success && mRetryCount > 0) {
+                            VvmLog.i(TAG, "fetch voicemail failed, retrying");
                             mRetryCount--;
                         } else {
                             return;
diff --git a/src/com/android/phone/vvm/omtp/fetch/VoicemailFetchedCallback.java b/src/com/android/phone/vvm/omtp/fetch/VoicemailFetchedCallback.java
index 3862d54..387ca5a 100644
--- a/src/com/android/phone/vvm/omtp/fetch/VoicemailFetchedCallback.java
+++ b/src/com/android/phone/vvm/omtp/fetch/VoicemailFetchedCallback.java
@@ -20,8 +20,8 @@
 import android.content.Context;
 import android.net.Uri;
 import android.provider.VoicemailContract.Voicemails;
-import android.util.Log;
 
+import com.android.phone.vvm.omtp.VvmLog;
 import com.android.phone.vvm.omtp.imap.VoicemailPayload;
 
 import libcore.io.IoUtils;
@@ -51,7 +51,7 @@
      * @param voicemailPayload The object containing the content data for the voicemail
      */
     public void setVoicemailContent(VoicemailPayload voicemailPayload) {
-        Log.d(TAG, String.format("Writing new voicemail content: %s", mUri));
+        VvmLog.d(TAG, String.format("Writing new voicemail content: %s", mUri));
         OutputStream outputStream = null;
 
         try {
@@ -61,7 +61,7 @@
                 outputStream.write(inputBytes);
             }
         } catch (IOException e) {
-            Log.w(TAG, String.format("File not found for %s", mUri));
+            VvmLog.w(TAG, String.format("File not found for %s", mUri));
             return;
         } finally {
             IoUtils.closeQuietly(outputStream);
@@ -73,7 +73,8 @@
         values.put(Voicemails.HAS_CONTENT, true);
         int updatedCount = mContentResolver.update(mUri, values, null, null);
         if (updatedCount != 1) {
-            Log.e(TAG, "Updating voicemail should have updated 1 row, was: " + updatedCount);
+            VvmLog
+                    .e(TAG, "Updating voicemail should have updated 1 row, was: " + updatedCount);
         }
     }
 }
diff --git a/src/com/android/phone/vvm/omtp/imap/ImapHelper.java b/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
index ce9e9c3..216b6a4 100644
--- a/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
+++ b/src/com/android/phone/vvm/omtp/imap/ImapHelper.java
@@ -25,7 +25,6 @@
 import android.telecom.PhoneAccountHandle;
 import android.telecom.Voicemail;
 import android.util.Base64;
-import android.util.Log;
 
 import com.android.phone.PhoneUtils;
 import com.android.phone.VoicemailStatus;
@@ -50,6 +49,7 @@
 import com.android.phone.vvm.omtp.OmtpConstants.ChangePinResult;
 import com.android.phone.vvm.omtp.OmtpEvents;
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
+import com.android.phone.vvm.omtp.VvmLog;
 import com.android.phone.vvm.omtp.fetch.VoicemailFetchedCallback;
 import com.android.phone.vvm.omtp.sync.OmtpVvmSyncService.TranscriptionFetchedCallback;
 
@@ -135,11 +135,11 @@
         return mImapStore != null;
     }
 
-    public boolean isRoaming(){
+    public boolean isRoaming() {
         ConnectivityManager connectivityManager = (ConnectivityManager) mContext.getSystemService(
                 Context.CONNECTIVITY_SERVICE);
         NetworkInfo info = connectivityManager.getNetworkInfo(mNetwork);
-        if(info == null){
+        if (info == null) {
             return false;
         }
         return info.isRoaming();
@@ -153,12 +153,16 @@
         return mImapStore.getConnection();
     }
 
-    /** The caller thread will block until the method returns. */
+    /**
+     * The caller thread will block until the method returns.
+     */
     public boolean markMessagesAsRead(List<Voicemail> voicemails) {
         return setFlags(voicemails, Flag.SEEN);
     }
 
-    /** The caller thread will block until the method returns. */
+    /**
+     * The caller thread will block until the method returns.
+     */
     public boolean markMessagesAsDeleted(List<Voicemail> voicemails) {
         return setFlags(voicemails, Flag.DELETED);
     }
@@ -232,7 +236,7 @@
      * transcription exists.
      */
     private Voicemail getVoicemailFromMessageStructure(
-            MessageStructureWrapper messageStructureWrapper) throws MessagingException{
+            MessageStructureWrapper messageStructureWrapper) throws MessagingException {
         Message messageDetails = messageStructureWrapper.messageStructure;
 
         TranscriptionFetchedListener listener = new TranscriptionFetchedListener();
@@ -240,7 +244,7 @@
             FetchProfile fetchProfile = new FetchProfile();
             fetchProfile.add(messageStructureWrapper.transcriptionBodyPart);
 
-            mFolder.fetch(new Message[] {messageDetails}, fetchProfile, listener);
+            mFolder.fetch(new Message[]{messageDetails}, fetchProfile, listener);
         }
 
         // Found an audio attachment, this is a valid voicemail.
@@ -257,8 +261,8 @@
     }
 
     /**
-     * The "from" field of a visual voicemail IMAP message is the number of the caller who left
-     * the message. Extract this number from the list of "from" addresses.
+     * The "from" field of a visual voicemail IMAP message is the number of the caller who left the
+     * message. Extract this number from the list of "from" addresses.
      *
      * @param fromAddresses A list of addresses that comprise the "from" line.
      * @return The number of the voicemail sender.
@@ -297,7 +301,7 @@
 
         // The IMAP folder fetch method will call "messageRetrieved" on the listener when the
         // message is successfully retrieved.
-        mFolder.fetch(new Message[] {message}, fetchProfile, listener);
+        mFolder.fetch(new Message[]{message}, fetchProfile, listener);
         return listener.getMessageStructure();
     }
 
@@ -341,7 +345,7 @@
         FetchProfile fetchProfile = new FetchProfile();
         fetchProfile.add(FetchProfile.Item.BODY);
 
-        mFolder.fetch(new Message[] {message}, fetchProfile, listener);
+        mFolder.fetch(new Message[]{message}, fetchProfile, listener);
         return listener.getVoicemailPayload();
     }
 
@@ -367,7 +371,7 @@
 
                     // This method is called synchronously so the transcription will be populated
                     // in the listener once the next method is called.
-                    mFolder.fetch(new Message[] {message}, fetchProfile, listener);
+                    mFolder.fetch(new Message[]{message}, fetchProfile, listener);
                     callback.setVoicemailTranscription(listener.getVoicemailTranscription());
                 }
             }
@@ -481,7 +485,7 @@
             return;
         }
         if (quota.occupied == mQuotaOccupied && quota.total == mQuotaTotal) {
-            Log.v(TAG, "Quota hasn't changed");
+            VvmLog.v(TAG, "Quota hasn't changed");
             return;
         }
         mQuotaOccupied = quota.occupied;
@@ -493,17 +497,20 @@
                 .putInt(getSharedPrefsKey(PREF_KEY_QUOTA_OCCUPIED), mQuotaOccupied)
                 .putInt(getSharedPrefsKey(PREF_KEY_QUOTA_TOTAL), mQuotaTotal)
                 .apply();
-        Log.v(TAG, "Quota changed to " + mQuotaOccupied + "/" + mQuotaTotal);
+        VvmLog.v(TAG, "Quota changed to " + mQuotaOccupied + "/" + mQuotaTotal);
     }
+
     /**
-     * A wrapper to hold a message with its header details and the structure for transcriptions
-     * (so they can be fetched in the future).
+     * A wrapper to hold a message with its header details and the structure for transcriptions (so
+     * they can be fetched in the future).
      */
     public class MessageStructureWrapper {
+
         public Message messageStructure;
         public BodyPart transcriptionBodyPart;
 
-        public MessageStructureWrapper() { }
+        public MessageStructureWrapper() {
+        }
     }
 
     /**
@@ -511,6 +518,7 @@
      */
     private final class MessageStructureFetchedListener
             implements ImapFolder.MessageRetrievalListener {
+
         private MessageStructureWrapper mMessageStructure;
 
         public MessageStructureFetchedListener() {
@@ -542,7 +550,6 @@
          * @param message The IMAP message.
          * @return The MessageStructureWrapper object corresponding to an IMAP message and
          * transcription.
-         * @throws MessagingException
          */
         private MessageStructureWrapper getMessageOrNull(Message message)
                 throws MessagingException {
@@ -579,9 +586,12 @@
      * Listener for the message body being fetched.
      */
     private final class MessageBodyFetchedListener implements ImapFolder.MessageRetrievalListener {
+
         private VoicemailPayload mVoicemailPayload;
 
-        /** Returns the fetch voicemail payload. */
+        /**
+         * Returns the fetch voicemail payload.
+         */
         public VoicemailPayload getVoicemailPayload() {
             return mVoicemailPayload;
         }
@@ -602,18 +612,18 @@
         private VoicemailPayload getVoicemailPayloadFromMessage(Message message)
                 throws MessagingException, IOException {
             Multipart multipart = (Multipart) message.getBody();
+            List<String> mimeTypes = new ArrayList<>();
             for (int i = 0; i < multipart.getCount(); ++i) {
                 BodyPart bodyPart = multipart.getBodyPart(i);
                 String bodyPartMimeType = bodyPart.getMimeType().toLowerCase();
-                LogUtils.d(TAG, "bodyPart mime type: " + bodyPartMimeType);
-
+                mimeTypes.add(bodyPartMimeType);
                 if (bodyPartMimeType.startsWith("audio/")) {
                     byte[] bytes = getDataFromBody(bodyPart.getBody());
                     LogUtils.d(TAG, String.format("Fetched %s bytes of data", bytes.length));
                     return new VoicemailPayload(bodyPartMimeType, bytes);
                 }
             }
-            LogUtils.e(TAG, "No audio attachment found on this voicemail");
+            LogUtils.e(TAG, "No audio attachment found on this voicemail, mimeTypes:" + mimeTypes);
             return null;
         }
     }
@@ -623,9 +633,12 @@
      */
     private final class TranscriptionFetchedListener implements
             ImapFolder.MessageRetrievalListener {
+
         private String mVoicemailTranscription;
 
-        /** Returns the fetched voicemail transcription. */
+        /**
+         * Returns the fetched voicemail transcription.
+         */
         public String getVoicemailTranscription() {
             return mVoicemailTranscription;
         }
diff --git a/src/com/android/phone/vvm/omtp/protocol/ProtocolHelper.java b/src/com/android/phone/vvm/omtp/protocol/ProtocolHelper.java
index d265bd0..748fd39 100644
--- a/src/com/android/phone/vvm/omtp/protocol/ProtocolHelper.java
+++ b/src/com/android/phone/vvm/omtp/protocol/ProtocolHelper.java
@@ -18,9 +18,9 @@
 
 import android.telephony.SmsManager;
 import android.text.TextUtils;
-import android.util.Log;
 
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
+import com.android.phone.vvm.omtp.VvmLog;
 import com.android.phone.vvm.omtp.sms.OmtpMessageSender;
 
 public class ProtocolHelper {
@@ -33,7 +33,7 @@
         int applicationPort = config.getApplicationPort();
         String destinationNumber = config.getDestinationNumber();
         if (TextUtils.isEmpty(destinationNumber)) {
-            Log.w(TAG, "No destination number for this carrier.");
+            VvmLog.w(TAG, "No destination number for this carrier.");
             return null;
         }
 
diff --git a/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java b/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java
index 9e56d12..be2a77f 100644
--- a/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java
+++ b/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocol.java
@@ -70,7 +70,8 @@
         return command;
     }
 
-    public void handleEvent(Context context, int subId, OmtpEvents event) {
-        DefaultOmtpEventHandler.handleEvent(context, subId, event);
+    public void handleEvent(Context context, OmtpVvmCarrierConfigHelper config,
+            OmtpEvents event) {
+        DefaultOmtpEventHandler.handleEvent(context, config, event);
     }
 }
diff --git a/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocolFactory.java b/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocolFactory.java
index dbf38c2..5f54a50 100644
--- a/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocolFactory.java
+++ b/src/com/android/phone/vvm/omtp/protocol/VisualVoicemailProtocolFactory.java
@@ -18,7 +18,8 @@
 
 import android.annotation.Nullable;
 import android.telephony.TelephonyManager;
-import android.util.Log;
+
+import com.android.phone.vvm.omtp.VvmLog;
 
 public class VisualVoicemailProtocolFactory {
 
@@ -39,7 +40,7 @@
             case VVM_TYPE_VVM3:
                 return new Vvm3Protocol();
             default:
-                Log.e(TAG, "Unexpected visual voicemail type: " + type);
+                VvmLog.e(TAG, "Unexpected visual voicemail type: " + type);
         }
         return null;
     }
diff --git a/src/com/android/phone/vvm/omtp/protocol/Vvm3EventHandler.java b/src/com/android/phone/vvm/omtp/protocol/Vvm3EventHandler.java
new file mode 100644
index 0000000..8eacb99
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/protocol/Vvm3EventHandler.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.phone.vvm.omtp.protocol;
+
+import android.annotation.IntDef;
+import android.content.Context;
+import android.telecom.PhoneAccountHandle;
+import android.util.Log;
+
+import com.android.phone.VoicemailStatus;
+import com.android.phone.settings.VoicemailChangePinDialogPreference;
+import com.android.phone.vvm.omtp.DefaultOmtpEventHandler;
+import com.android.phone.vvm.omtp.OmtpEvents;
+import com.android.phone.vvm.omtp.OmtpEvents.Type;
+import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
+import com.android.phone.vvm.omtp.utils.PhoneAccountHandleConverter;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Handles {@link OmtpEvents} when {@link Vvm3Protocol} is being used. This handler writes custom
+ * error codes into the voicemail status table so support on the dialer side is required.
+ *
+ * TODO(b/29577838) disable VVM3 by default so support on system dialer can be ensured.
+ */
+public class Vvm3EventHandler {
+
+    private static final String TAG = "Vvm3EventHandler";
+
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({VMS_DNS_FAILURE, VMG_DNS_FAILURE, SPG_DNS_FAILURE, VMS_NO_CELLULAR, VMG_NO_CELLULAR,
+            SPG_NO_CELLULAR, VMS_TIMEOUT, VMG_TIMEOUT, STATUS_SMS_TIMEOUT, SUBSCRIBER_BLOCKED,
+            UNKNOWN_USER, UNKNOWN_DEVICE, INVALID_PASSWORD, MAILBOX_NOT_INITIALIZED,
+            SERVICE_NOT_PROVISIONED, SERVICE_NOT_ACTIVATED, USER_BLOCKED, IMAP_GETQUOTA_ERROR,
+            IMAP_SELECT_ERROR, IMAP_ERROR, VMG_INTERNAL_ERROR, VMG_DB_ERROR,
+            VMG_COMMUNICATION_ERROR, SPG_URL_NOT_FOUND, VMG_UNKNOWN_ERROR, PIN_NOT_SET})
+    public @interface ErrorCode {
+
+    }
+
+    public static final int VMS_DNS_FAILURE = -9001;
+    public static final int VMG_DNS_FAILURE = -9002;
+    public static final int SPG_DNS_FAILURE = -9003;
+    public static final int VMS_NO_CELLULAR = -9004;
+    public static final int VMG_NO_CELLULAR = -9005;
+    public static final int SPG_NO_CELLULAR = -9006;
+    public static final int VMS_TIMEOUT = -9007;
+    public static final int VMG_TIMEOUT = -9008;
+    public static final int STATUS_SMS_TIMEOUT = -9009;
+
+    public static final int SUBSCRIBER_BLOCKED = -9990;
+    public static final int UNKNOWN_USER = -9991;
+    public static final int UNKNOWN_DEVICE = -9992;
+    public static final int INVALID_PASSWORD = -9993;
+    public static final int MAILBOX_NOT_INITIALIZED = -9994;
+    public static final int SERVICE_NOT_PROVISIONED = -9995;
+    public static final int SERVICE_NOT_ACTIVATED = -9996;
+    public static final int USER_BLOCKED = -9998;
+    public static final int IMAP_GETQUOTA_ERROR = -9997;
+    public static final int IMAP_SELECT_ERROR = -9989;
+    public static final int IMAP_ERROR = -9999;
+
+    public static final int VMG_INTERNAL_ERROR = -101;
+    public static final int VMG_DB_ERROR = -102;
+    public static final int VMG_COMMUNICATION_ERROR = -103;
+    public static final int SPG_URL_NOT_FOUND = -301;
+
+    // Non VVM3 codes:
+    public static final int VMG_UNKNOWN_ERROR = -1;
+    public static final int PIN_NOT_SET = -100;
+
+
+    public static void handleEvent(Context context, OmtpVvmCarrierConfigHelper config,
+            OmtpEvents event) {
+        boolean handled = false;
+        switch (event.getType()) {
+            case Type.CONFIGURATION:
+                handled = handleConfigurationEvent(context, config, event);
+                break;
+            case Type.DATA_CHANNEL:
+                handled = handleDataChannelEvent(context, config, event);
+                break;
+            case Type.NOTIFICATION_CHANNEL:
+                handled = handleNotificationChannelEvent(context, config, event);
+                break;
+            case Type.OTHER:
+                handled = handleOtherEvent(context, config, event);
+                break;
+            default:
+                com.android.services.telephony.Log
+                        .wtf(TAG, "invalid event type " + event.getType() + " for " + event);
+        }
+        if (!handled) {
+            DefaultOmtpEventHandler.handleEvent(context, config, event);
+        }
+    }
+
+    private static boolean handleConfigurationEvent(Context context,
+            OmtpVvmCarrierConfigHelper config, OmtpEvents event) {
+        switch (event) {
+            case CONFIG_REQUEST_STATUS_SUCCESS:
+                PhoneAccountHandle handle = PhoneAccountHandleConverter
+                        .fromSubId(config.getSubId());
+                if (VoicemailChangePinDialogPreference.getDefaultOldPin(context, handle) == null) {
+                    return false;
+                } else {
+                    postError(context, config, PIN_NOT_SET);
+                }
+                break;
+            case CONFIG_DEFAULT_PIN_REPLACED:
+                postError(context, config, PIN_NOT_SET);
+                break;
+            default:
+                return false;
+        }
+        return true;
+    }
+
+    private static boolean handleDataChannelEvent(Context context,
+            OmtpVvmCarrierConfigHelper config, OmtpEvents event) {
+        switch (event) {
+            case DATA_NO_CONNECTION:
+            case DATA_NO_CONNECTION_CELLULAR_REQUIRED:
+                postError(context, config, VMS_NO_CELLULAR);
+                break;
+            case DATA_CANNOT_RESOLVE_HOST_ON_NETWORK:
+                postError(context, config, VMS_DNS_FAILURE);
+                break;
+            case DATA_BAD_IMAP_CREDENTIAL:
+                postError(context, config, IMAP_ERROR);
+                break;
+            case DATA_AUTH_UNKNOWN_USER:
+                postError(context, config, UNKNOWN_USER);
+                break;
+            case DATA_AUTH_UNKNOWN_DEVICE:
+                postError(context, config, UNKNOWN_DEVICE);
+                break;
+            case DATA_AUTH_INVALID_PASSWORD:
+                postError(context, config, INVALID_PASSWORD);
+                break;
+            case DATA_AUTH_MAILBOX_NOT_INITIALIZED:
+                postError(context, config, MAILBOX_NOT_INITIALIZED);
+                break;
+            case DATA_AUTH_SERVICE_NOT_PROVISIONED:
+                postError(context, config, SERVICE_NOT_PROVISIONED);
+                break;
+            case DATA_AUTH_SERVICE_NOT_ACTIVATED:
+                postError(context, config, SERVICE_NOT_ACTIVATED);
+                break;
+            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;
+        }
+        return true;
+    }
+
+    private static boolean handleNotificationChannelEvent(Context context,
+            OmtpVvmCarrierConfigHelper config, OmtpEvents event) {
+        return false;
+    }
+
+    private static boolean handleOtherEvent(Context context, OmtpVvmCarrierConfigHelper config,
+            OmtpEvents event) {
+        switch (event) {
+            case VVM3_NEW_USER_SETUP_FAILED:
+                postError(context, config, MAILBOX_NOT_INITIALIZED);
+                break;
+            case VVM3_VMG_DNS_FAILURE:
+                postError(context, config, VMG_DNS_FAILURE);
+                break;
+            case VVM3_SPG_DNS_FAILURE:
+                postError(context, config, SPG_DNS_FAILURE);
+                break;
+            case VVM3_VMG_CONNECTION_FAILED:
+                postError(context, config, VMG_NO_CELLULAR);
+                break;
+            case VVM3_SPG_CONNECTION_FAILED:
+                postError(context, config, SPG_NO_CELLULAR);
+                break;
+            case VVM3_VMG_TIMEOUT:
+                postError(context, config, VMG_TIMEOUT);
+                break;
+
+            case VVM3_SUBSCRIBER_PROVISIONED:
+                postError(context, config, SERVICE_NOT_ACTIVATED);
+            case VVM3_SUBSCRIBER_BLOCKED:
+                postError(context, config, SUBSCRIBER_BLOCKED);
+            default:
+                return false;
+        }
+        return true;
+    }
+
+    private static void postError(Context context, OmtpVvmCarrierConfigHelper config,
+            @ErrorCode int errorCode) {
+        VoicemailStatus.Editor editor = VoicemailStatus.edit(context, config.getSubId());
+
+        switch (errorCode) {
+            case VMG_DNS_FAILURE:
+            case SPG_DNS_FAILURE:
+            case VMG_NO_CELLULAR:
+            case SPG_NO_CELLULAR:
+            case VMG_TIMEOUT:
+            case SUBSCRIBER_BLOCKED:
+            case UNKNOWN_USER:
+            case UNKNOWN_DEVICE:
+            case INVALID_PASSWORD:
+            case MAILBOX_NOT_INITIALIZED:
+            case SERVICE_NOT_PROVISIONED:
+            case SERVICE_NOT_ACTIVATED:
+            case USER_BLOCKED:
+            case VMG_UNKNOWN_ERROR:
+            case SPG_URL_NOT_FOUND:
+            case VMG_INTERNAL_ERROR:
+            case VMG_DB_ERROR:
+            case VMG_COMMUNICATION_ERROR:
+            case PIN_NOT_SET:
+                editor.setConfigurationState(errorCode);
+                break;
+            case VMS_NO_CELLULAR:
+            case VMS_DNS_FAILURE:
+            case VMS_TIMEOUT:
+            case IMAP_GETQUOTA_ERROR:
+            case IMAP_SELECT_ERROR:
+            case IMAP_ERROR:
+                editor.setDataChannelState(errorCode);
+                break;
+            case STATUS_SMS_TIMEOUT:
+                editor.setNotificationChannelState(errorCode);
+                break;
+            default:
+                Log.wtf(TAG, "unknown error code: " + errorCode);
+        }
+        editor.apply();
+    }
+}
diff --git a/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java b/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java
index e62d1cf..306006d 100644
--- a/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java
+++ b/src/com/android/phone/vvm/omtp/protocol/Vvm3Protocol.java
@@ -17,17 +17,19 @@
 package com.android.phone.vvm.omtp.protocol;
 
 import android.annotation.Nullable;
+import android.content.Context;
 import android.net.Network;
 import android.os.Bundle;
 import android.telecom.PhoneAccountHandle;
 import android.telephony.SmsManager;
-import android.util.Log;
 
 import com.android.phone.common.mail.MessagingException;
 import com.android.phone.settings.VisualVoicemailSettingsUtil;
 import com.android.phone.settings.VoicemailChangePinDialogPreference;
 import com.android.phone.vvm.omtp.OmtpConstants;
+import com.android.phone.vvm.omtp.OmtpEvents;
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
+import com.android.phone.vvm.omtp.VvmLog;
 import com.android.phone.vvm.omtp.imap.ImapHelper;
 import com.android.phone.vvm.omtp.sms.OmtpMessageSender;
 import com.android.phone.vvm.omtp.sms.StatusMessage;
@@ -53,20 +55,20 @@
 
     private static String ISO639_Spanish = "es";
 
-    private static String VVM3_VM_LANGUAGE_ENGLISH_STANDARD = "1";
-    private static String VVM3_VM_LANGUAGE_SPANISH_STANDARD = "2";
+    // Default prompt level when using the telephone user interface.
+    // Standard prompt when the user call into the voicemail, and no prompts when someone else is
+    // leaving a voicemail.
+    private static String VVM3_VM_LANGUAGE_ENGLISH_STANDARD_NO_GUEST_PROMPTS = "5";
+    private static String VVM3_VM_LANGUAGE_SPANISH_STANDARD_NO_GUEST_PROMPTS = "6";
 
     private static final int PIN_LENGTH = 6;
 
-    public Vvm3Protocol() {
-        Log.d(TAG, "Vvm3Protocol created");
-    }
-
     @Override
     public void startActivation(OmtpVvmCarrierConfigHelper config) {
         // VVM3 does not support activation SMS.
         // Send a status request which will start the provisioning process if the user is not
         // provisioned.
+        VvmLog.i(TAG, "Activating");
         config.requestStatus();
     }
 
@@ -79,15 +81,22 @@
     @Override
     public void startProvisioning(PhoneAccountHandle phoneAccountHandle,
             OmtpVvmCarrierConfigHelper config, StatusMessage message, Bundle data) {
-        Log.i(TAG, "start vvm3 provisioning");
-        if ("U".equals(message.getProvisioningStatus())) {
-            Log.i(TAG, "Provisioning status: Unknown, subscribing");
+        VvmLog.i(TAG, "start vvm3 provisioning");
+        if (OmtpConstants.SUBSCRIBER_UNKNOWN.equals(message.getProvisioningStatus())) {
+            VvmLog.i(TAG, "Provisioning status: Unknown, subscribing");
             new Vvm3Subscriber(phoneAccountHandle, config, data).subscribe();
-        } else if ("N".equals(message.getProvisioningStatus())) {
-            Log.i(TAG, "setting up new user");
+        } else if (OmtpConstants.SUBSCRIBER_NEW.equals(message.getProvisioningStatus())) {
+            VvmLog.i(TAG, "setting up new user");
             VisualVoicemailSettingsUtil.setVisualVoicemailCredentialsFromStatusMessage(
                     config.getContext(), phoneAccountHandle, message);
             startProvisionNewUser(phoneAccountHandle, config, message);
+        } else if (OmtpConstants.SUBSCRIBER_PROVISIONED.equals(message.getProvisioningStatus())) {
+            VvmLog.i(TAG, "User provisioned but not activated, disabling VVM");
+            VisualVoicemailSettingsUtil
+                    .setVisualVoicemailEnabled(config.getContext(), phoneAccountHandle, false);
+        } else if (OmtpConstants.SUBSCRIBER_BLOCKED.equals(message.getProvisioningStatus())) {
+            VvmLog.i(TAG, "User blocked");
+            config.handleEvent(OmtpEvents.VVM3_SUBSCRIBER_BLOCKED);
         }
     }
 
@@ -98,6 +107,11 @@
     }
 
     @Override
+    public void handleEvent(Context context, OmtpVvmCarrierConfigHelper config, OmtpEvents event) {
+        Vvm3EventHandler.handleEvent(context, config, event);
+    }
+
+    @Override
     public String getCommand(String command) {
         if (command == OmtpConstants.IMAP_CHANGE_TUI_PWD_FORMAT) {
             return IMAP_CHANGE_TUI_PWD_FORMAT;
@@ -132,7 +146,7 @@
         @Override
         public void onAvailable(Network network) {
             super.onAvailable(network);
-            Log.i(TAG, "new user: network available");
+            VvmLog.i(TAG, "new user: network available");
             ImapHelper helper = new ImapHelper(mContext, mPhoneAccount, network);
 
             try {
@@ -143,45 +157,48 @@
                 if (Locale.getDefault().getLanguage()
                         .equals(new Locale(ISO639_Spanish).getLanguage())) {
                     // Spanish
-                    helper.changeVoicemailTuiLanguage(VVM3_VM_LANGUAGE_SPANISH_STANDARD);
+                    helper.changeVoicemailTuiLanguage(
+                            VVM3_VM_LANGUAGE_SPANISH_STANDARD_NO_GUEST_PROMPTS);
                 } else {
                     // English
-                    helper.changeVoicemailTuiLanguage(VVM3_VM_LANGUAGE_ENGLISH_STANDARD);
+                    helper.changeVoicemailTuiLanguage(
+                            VVM3_VM_LANGUAGE_ENGLISH_STANDARD_NO_GUEST_PROMPTS);
                 }
-                Log.i(TAG, "new user: language set");
+                VvmLog.i(TAG, "new user: language set");
 
                 if (setPin(helper)) {
                     // Only close new user tutorial if the PIN has been changed.
                     helper.closeNewUserTutorial();
-                    Log.i(TAG, "new user: NUT closed");
+                    VvmLog.i(TAG, "new user: NUT closed");
 
                     mConfig.requestStatus();
                 }
             } catch (MessagingException | IOException e) {
-                Log.e(TAG, e.toString());
+                helper.handleEvent(OmtpEvents.VVM3_NEW_USER_SETUP_FAILED);
+                VvmLog.e(TAG, e.toString());
             }
         }
 
         private boolean setPin(ImapHelper helper) throws IOException, MessagingException {
             String defaultPin = getDefaultPin();
             if (defaultPin == null) {
+                VvmLog.i(TAG, "cannot generate default PIN");
                 return false;
             }
 
             if (VoicemailChangePinDialogPreference.getDefaultOldPin(mContext, mPhoneAccount)
                     != null) {
                 // The pin was already set
+                VvmLog.i(TAG, "PIN already set");
                 return true;
             }
             String newPin = generatePin();
             if (helper.changePin(defaultPin, newPin) == OmtpConstants.CHANGE_PIN_SUCCESS) {
                 VoicemailChangePinDialogPreference
                         .setDefaultOldPIN(mContext, mPhoneAccount, newPin);
-
-                // TODO(b/29082418): set CONFIGURATION_STATE to VVM3_CONFIGURATION_PIN_NOT_SET
-                // to prompt the user to set the PIN
+                helper.handleEvent(OmtpEvents.CONFIG_DEFAULT_PIN_REPLACED);
             }
-            Log.i(TAG, "new user: PIN set");
+            VvmLog.i(TAG, "new user: PIN set");
             return true;
         }
 
@@ -192,12 +209,12 @@
             try {
                 String number = username.substring(0, username.indexOf('@'));
                 if (number.length() < 4) {
-                    Log.e(TAG, "unable to extract number from IMAP username");
+                    VvmLog.e(TAG, "unable to extract number from IMAP username");
                     return null;
                 }
                 return "1" + number.substring(number.length() - 4);
             } catch (StringIndexOutOfBoundsException e) {
-                Log.e(TAG, "unable to extract number from IMAP username");
+                VvmLog.e(TAG, "unable to extract number from IMAP username");
                 return null;
             }
 
diff --git a/src/com/android/phone/vvm/omtp/protocol/Vvm3Subscriber.java b/src/com/android/phone/vvm/omtp/protocol/Vvm3Subscriber.java
index c314ff5..980701e 100644
--- a/src/com/android/phone/vvm/omtp/protocol/Vvm3Subscriber.java
+++ b/src/com/android/phone/vvm/omtp/protocol/Vvm3Subscriber.java
@@ -26,10 +26,11 @@
 import android.text.Spanned;
 import android.text.style.URLSpan;
 import android.util.ArrayMap;
-import android.util.Log;
 
 import com.android.phone.Assert;
+import com.android.phone.vvm.omtp.OmtpEvents;
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
+import com.android.phone.vvm.omtp.VvmLog;
 import com.android.phone.vvm.omtp.sync.VvmNetworkRequestCallback;
 import com.android.volley.AuthFailureError;
 import com.android.volley.Request;
@@ -151,6 +152,7 @@
         Assert.isNotMainThread();
         // Cellular data is required to subscribe.
         // processSubscription() is called after network is available.
+        VvmLog.i(TAG, "Subscribing");
         new Vvm3ProvisioningNetworkRequestCallback(mHelper, mHandle).requestNetwork();
     }
 
@@ -161,7 +163,7 @@
             String subscribeLink = findSubscribeLink(selfProvisionResponse);
             clickSubscribeLink(subscribeLink);
         } catch (ProvisioningException e) {
-            Log.e(TAG, e.toString());
+            VvmLog.e(TAG, e.toString());
         }
     }
 
@@ -169,6 +171,7 @@
      * Get the URL to perform self-provisioning from the voicemail management gateway.
      */
     private String getSelfProvisioningGateway() throws ProvisioningException {
+        VvmLog.i(TAG, "retrieving SPG URL");
         String response = vvm3XmlRequest(OPERATION_GET_SPG_URL);
         return extractText(response, SPG_URL_TAG);
     }
@@ -179,6 +182,8 @@
      * subscription. The cookie from this response and cellular data is required to click the link.
      */
     private String getSelfProvisionResponse(String url) throws ProvisioningException {
+        VvmLog.i(TAG, "Retrieving self provisioning response");
+
         RequestFuture<String> future = RequestFuture.newFuture();
 
         StringRequest stringRequest = new StringRequest(Request.Method.POST, url, future, future) {
@@ -200,11 +205,13 @@
         try {
             return future.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
         } catch (InterruptedException | ExecutionException | TimeoutException e) {
+            mHelper.handleEvent(OmtpEvents.VVM3_SPG_CONNECTION_FAILED);
             throw new ProvisioningException(e.toString());
         }
     }
 
     private void clickSubscribeLink(String subscribeLink) throws ProvisioningException {
+        VvmLog.i(TAG, "Clicking subscribe link");
         RequestFuture<String> future = RequestFuture.newFuture();
 
         StringRequest stringRequest = new StringRequest(Request.Method.POST,
@@ -214,15 +221,16 @@
         try {
             future.get(REQUEST_TIMEOUT_SECONDS, TimeUnit.SECONDS);
         } catch (InterruptedException | ExecutionException | TimeoutException e) {
+            mHelper.handleEvent(OmtpEvents.VVM3_SPG_CONNECTION_FAILED);
             throw new ProvisioningException(e.toString());
         }
     }
 
     private String vvm3XmlRequest(String operation) throws ProvisioningException {
-        Log.d(TAG, "Sending vvm3XmlRequest for " + operation);
+        VvmLog.d(TAG, "Sending vvm3XmlRequest for " + operation);
         String voicemailManagementGateway = mData.getString(VMG_URL_KEY);
         if (voicemailManagementGateway == null) {
-            Log.e(TAG, "voicemailManagementGateway url unknown");
+            VvmLog.e(TAG, "voicemailManagementGateway url unknown");
             return null;
         }
         String transactionId = createTransactionId();
@@ -246,6 +254,7 @@
             }
             return response;
         } catch (InterruptedException | ExecutionException | TimeoutException e) {
+            mHelper.handleEvent(OmtpEvents.VVM3_VMG_CONNECTION_FAILED);
             throw new ProvisioningException(e.toString());
         }
     }
@@ -285,7 +294,7 @@
         @Override
         public void onAvailable(Network network) {
             super.onAvailable(network);
-            Log.d(TAG, "provisioning: network available");
+            VvmLog.d(TAG, "provisioning: network available");
             mRequestQueue = Volley
                     .newRequestQueue(mContext, new NetworkSpecifiedHurlStack(network));
             processSubscription();
diff --git a/src/com/android/phone/vvm/omtp/sms/LegacyModeSmsHandler.java b/src/com/android/phone/vvm/omtp/sms/LegacyModeSmsHandler.java
new file mode 100644
index 0000000..ba5bd70
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/sms/LegacyModeSmsHandler.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2016 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.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.provider.VoicemailContract;
+import android.telecom.PhoneAccountHandle;
+
+import com.android.internal.telephony.Phone;
+import com.android.phone.PhoneUtils;
+import com.android.phone.vvm.omtp.OmtpConstants;
+import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
+import com.android.phone.vvm.omtp.VvmLog;
+
+/**
+ * Class ot handle voicemail SMS under legacy mode
+ *
+ * @see OmtpVvmCarrierConfigHelper#isLegacyModeEnabled()
+ */
+public class LegacyModeSmsHandler {
+
+    private static final String TAG = "LegacyModeSmsHandler";
+
+    public static void handle(Context context, Intent intent, PhoneAccountHandle handle) {
+        VvmLog.v(TAG, "processing VVM SMS on legacy mode");
+        String eventType = intent.getExtras()
+                .getString(VoicemailContract.EXTRA_VOICEMAIL_SMS_PREFIX);
+        Bundle data = intent.getExtras().getBundle(VoicemailContract.EXTRA_VOICEMAIL_SMS_FIELDS);
+
+        if (eventType.equals(OmtpConstants.SYNC_SMS_PREFIX)) {
+            SyncMessage message = new SyncMessage(data);
+            VvmLog.v(TAG, "Received SYNC sms for " + handle.getId() +
+                    " with event " + message.getSyncTriggerEvent());
+
+            switch (message.getSyncTriggerEvent()) {
+                case OmtpConstants.NEW_MESSAGE:
+                case OmtpConstants.MAILBOX_UPDATE:
+                    // The user has called into the voicemail and the new message count could
+                    // change.
+                    // For some carriers new message count could be set to 0 even if there are still
+                    // unread messages, to clear the message waiting indicator.
+                    VvmLog.v(TAG, "updating MWI");
+                    Phone phone = PhoneUtils.getPhoneForPhoneAccountHandle(handle);
+                    // Setting voicemail message count to non-zero will show the telephony voicemail
+                    // notification, and zero will clear it.
+                    phone.setVoiceMessageCount(message.getNewMessageCount());
+                    break;
+                default:
+                    break;
+            }
+        }
+    }
+}
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
index feb3c5a..c930a98 100644
--- a/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpMessageReceiver.java
@@ -25,14 +25,13 @@
 import android.provider.VoicemailContract;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.Voicemail;
-import android.util.Log;
 
 import com.android.phone.PhoneGlobals;
 import com.android.phone.settings.VisualVoicemailSettingsUtil;
-import com.android.phone.vvm.omtp.LocalLogHelper;
 import com.android.phone.vvm.omtp.OmtpConstants;
 import com.android.phone.vvm.omtp.OmtpEvents;
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
+import com.android.phone.vvm.omtp.VvmLog;
 import com.android.phone.vvm.omtp.sync.OmtpVvmSourceManager;
 import com.android.phone.vvm.omtp.sync.OmtpVvmSyncService;
 import com.android.phone.vvm.omtp.sync.VoicemailsQueryHelper;
@@ -49,7 +48,7 @@
     @Override
     public void onReceive(Context context, Intent intent) {
         if (!UserManager.get(context).isUserUnlocked()) {
-            Log.i(TAG, "Received message on locked device");
+            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;
         }
@@ -59,12 +58,17 @@
         PhoneAccountHandle phone = PhoneAccountHandleConverter.fromSubId(subId);
 
         if (phone == null) {
-            Log.i(TAG, "Received message for null phone account");
+            VvmLog.i(TAG, "Received message for null phone account");
             return;
         }
 
+        OmtpVvmCarrierConfigHelper helper = new OmtpVvmCarrierConfigHelper(mContext, subId);
         if (!VisualVoicemailSettingsUtil.isVisualVoicemailEnabled(mContext, phone)) {
-            Log.i(TAG, "Received vvm message for disabled vvm source.");
+            if (helper.isLegacyModeEnabled()) {
+                LegacyModeSmsHandler.handle(context, intent, phone);
+            } else {
+                VvmLog.i(TAG, "Received vvm message for disabled vvm source.");
+            }
             return;
         }
 
@@ -75,23 +79,20 @@
         if (eventType.equals(OmtpConstants.SYNC_SMS_PREFIX)) {
             SyncMessage message = new SyncMessage(data);
 
-            Log.v(TAG, "Received SYNC sms for " + phone.getId() +
-                    " with event " + message.getSyncTriggerEvent());
-            LocalLogHelper.log(TAG, "Received SYNC sms for " + phone.getId() +
+            VvmLog.v(TAG, "Received SYNC sms for " + subId +
                     " with event " + message.getSyncTriggerEvent());
             processSync(phone, message);
         } else if (eventType.equals(OmtpConstants.STATUS_SMS_PREFIX)) {
-            Log.v(TAG, "Received STATUS sms for " + phone.getId());
-            LocalLogHelper.log(TAG, "Received Status sms for " + phone.getId());
+            VvmLog.v(TAG, "Received Status sms for " + subId);
             StatusMessage message = new StatusMessage(data);
             if (message.getProvisioningStatus().equals(OmtpConstants.SUBSCRIBER_READY)) {
                 updateSource(phone, subId, message);
             } else {
-                Log.v(TAG, "Subscriber not ready, start provisioning");
+                VvmLog.v(TAG, "Subscriber not ready, start provisioning");
                 mContext.startService(OmtpProvisioningService.getProvisionIntent(mContext, intent));
             }
         } else {
-            Log.e(TAG, "Unknown prefix: " + eventType);
+            VvmLog.e(TAG, "Unknown prefix: " + eventType);
         }
     }
 
@@ -133,7 +134,8 @@
                 // Not implemented in V1
                 break;
             default:
-               Log.e(TAG, "Unrecognized sync trigger event: " + message.getSyncTriggerEvent());
+                VvmLog.e(TAG,
+                        "Unrecognized sync trigger event: " + message.getSyncTriggerEvent());
                break;
         }
 
@@ -166,11 +168,7 @@
 
             PhoneGlobals.getInstance().clearMwiIndicator(subId);
         } else {
-            Log.w(TAG, "Visual voicemail not available for subscriber.");
-            // Override default isEnabled setting to false since visual voicemail is unable to
-            // be accessed for some reason.
-            VisualVoicemailSettingsUtil.setVisualVoicemailEnabled(mContext, phone,
-                    /* isEnabled */ false, /* isUserSet */ true);
+            VvmLog.e(TAG, "Visual voicemail not available for subscriber.");
         }
     }
 }
diff --git a/src/com/android/phone/vvm/omtp/sms/OmtpMessageSender.java b/src/com/android/phone/vvm/omtp/sms/OmtpMessageSender.java
index 9080292..9a775f0 100644
--- a/src/com/android/phone/vvm/omtp/sms/OmtpMessageSender.java
+++ b/src/com/android/phone/vvm/omtp/sms/OmtpMessageSender.java
@@ -20,9 +20,10 @@
 import android.telephony.SmsManager;
 
 import com.android.phone.vvm.omtp.OmtpConstants;
-import com.android.services.telephony.Log;
+import com.android.phone.vvm.omtp.VvmLog;
 
 import java.io.UnsupportedEncodingException;
+import java.util.Locale;
 
 /**
  * Send client originated OMTP messages to the OMTP server.
@@ -73,9 +74,10 @@
     protected 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));
+            VvmLog
+                    .v(TAG, String.format("Sending TEXT sms '%s' to %s", text, mDestinationNumber));
             mSmsManager.sendTextMessageWithSelfPermissions(mDestinationNumber, null, text,
-                    sentIntent, null);
+                    sentIntent, null, false);
         } else {
             byte[] data;
             try {
@@ -83,8 +85,9 @@
             } 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));
+            VvmLog.v(TAG,
+                    String.format(Locale.US, "Sending BINARY sms '%s' to %s:%d", text,
+                            mDestinationNumber, mApplicationPort));
             mSmsManager.sendDataMessageWithSelfPermissions(mDestinationNumber, null,
                     mApplicationPort, data, sentIntent, null);
         }
diff --git a/src/com/android/phone/vvm/omtp/sms/StatusMessage.java b/src/com/android/phone/vvm/omtp/sms/StatusMessage.java
index ee1f07d..f9d972f 100644
--- a/src/com/android/phone/vvm/omtp/sms/StatusMessage.java
+++ b/src/com/android/phone/vvm/omtp/sms/StatusMessage.java
@@ -18,6 +18,7 @@
 import android.os.Bundle;
 import android.telecom.Log;
 
+import com.android.phone.NeededForTesting;
 import com.android.phone.vvm.omtp.OmtpConstants;
 
 /**
@@ -61,19 +62,19 @@
     }
 
     public StatusMessage(Bundle wrappedData) {
-        mProvisioningStatus = unquote(wrappedData.getString(OmtpConstants.PROVISIONING_STATUS));
-        mStatusReturnCode = wrappedData.getString(OmtpConstants.RETURN_CODE);
-        mSubscriptionUrl = wrappedData.getString(OmtpConstants.SUBSCRIPTION_URL);
-        mServerAddress = wrappedData.getString(OmtpConstants.SERVER_ADDRESS);
-        mTuiAccessNumber = wrappedData.getString(OmtpConstants.TUI_ACCESS_NUMBER);
-        mClientSmsDestinationNumber = wrappedData.getString(
+        mProvisioningStatus = unquote(getString(wrappedData, OmtpConstants.PROVISIONING_STATUS));
+        mStatusReturnCode = getString(wrappedData, OmtpConstants.RETURN_CODE);
+        mSubscriptionUrl = getString(wrappedData, OmtpConstants.SUBSCRIPTION_URL);
+        mServerAddress = getString(wrappedData, OmtpConstants.SERVER_ADDRESS);
+        mTuiAccessNumber = getString(wrappedData, OmtpConstants.TUI_ACCESS_NUMBER);
+        mClientSmsDestinationNumber = getString(wrappedData,
                 OmtpConstants.CLIENT_SMS_DESTINATION_NUMBER);
-        mImapPort = wrappedData.getString(OmtpConstants.IMAP_PORT);
-        mImapUserName = wrappedData.getString(OmtpConstants.IMAP_USER_NAME);
-        mImapPassword = wrappedData.getString(OmtpConstants.IMAP_PASSWORD);
-        mSmtpPort = wrappedData.getString(OmtpConstants.SMTP_PORT);
-        mSmtpUserName = wrappedData.getString(OmtpConstants.SMTP_USER_NAME);
-        mSmtpPassword = wrappedData.getString(OmtpConstants.SMTP_PASSWORD);
+        mImapPort = getString(wrappedData, OmtpConstants.IMAP_PORT);
+        mImapUserName = getString(wrappedData, OmtpConstants.IMAP_USER_NAME);
+        mImapPassword = getString(wrappedData, OmtpConstants.IMAP_PASSWORD);
+        mSmtpPort = getString(wrappedData, OmtpConstants.SMTP_PORT);
+        mSmtpUserName = getString(wrappedData, OmtpConstants.SMTP_USER_NAME);
+        mSmtpPassword = getString(wrappedData, OmtpConstants.SMTP_PASSWORD);
     }
 
     private static String unquote(String string) {
@@ -104,6 +105,7 @@
      * @return the URL of the voicemail server. This is the URL to send the users to for subscribing
      * to the visual voicemail service.
      */
+    @NeededForTesting
     public String getSubscriptionUrl() {
         return mSubscriptionUrl;
     }
@@ -120,6 +122,7 @@
      * @return the Telephony User Interface number to call to access voicemails directly from the
      * IVR.
      */
+    @NeededForTesting
     public String getTuiAccessNumber() {
         return mTuiAccessNumber;
     }
@@ -127,6 +130,7 @@
     /**
      * @return the number to which client originated SMSes should be sent to.
      */
+    @NeededForTesting
     public String getClientSmsDestinationNumber() {
         return mClientSmsDestinationNumber;
     }
@@ -155,6 +159,7 @@
     /**
      * @return the SMTP server port to talk to.
      */
+    @NeededForTesting
     public String getSmtpPort() {
         return mSmtpPort;
     }
@@ -162,6 +167,7 @@
     /**
      * @return the SMTP user name to be used for SMTP authentication.
      */
+    @NeededForTesting
     public String getSmtpUserName() {
         return mSmtpUserName;
     }
@@ -169,7 +175,16 @@
     /**
      * @return the SMTP password to be used for SMTP authentication.
      */
+    @NeededForTesting
     public String getSmtpPassword() {
         return mSmtpPassword;
     }
+
+    private static String getString(Bundle bundle, String key) {
+        String value = bundle.getString(key);
+        if (value == null) {
+            return "";
+        }
+        return value;
+    }
 }
\ 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
index 4dddba4..632ff9e 100644
--- a/src/com/android/phone/vvm/omtp/sms/SyncMessage.java
+++ b/src/com/android/phone/vvm/omtp/sms/SyncMessage.java
@@ -18,6 +18,7 @@
 import android.annotation.Nullable;
 import android.os.Bundle;
 
+import com.android.phone.NeededForTesting;
 import com.android.phone.vvm.omtp.OmtpConstants;
 
 import java.text.ParseException;
@@ -33,18 +34,17 @@
     // Sync event that triggered this message.
     private final String mSyncTriggerEvent;
     // Total number of new messages on the server.
-    private final Integer mNewMessageCount;
+    private final int mNewMessageCount;
     // UID of the new message.
     private final String mMessageId;
     // Length of the message.
-    @Nullable
-    private final Integer mMessageLength;
+    private final int 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;
+    private final long mMsgTimeMillis;
 
     @Override
     public String toString() {
@@ -58,21 +58,19 @@
     }
 
     public SyncMessage(Bundle wrappedData) {
-        mSyncTriggerEvent = wrappedData.getString(OmtpConstants.SYNC_TRIGGER_EVENT);
-        mMessageId = wrappedData.getString(OmtpConstants.MESSAGE_UID);
-        if (wrappedData.getString(OmtpConstants.MESSAGE_LENGTH) != null) {
-            mMessageLength = Integer.parseInt(wrappedData.getString(OmtpConstants.MESSAGE_LENGTH));
-        } else {
-            // Optional field
-            mMessageLength = null;
-        }
-        mContentType = wrappedData.getString(OmtpConstants.CONTENT_TYPE);
-        mSender = wrappedData.getString(OmtpConstants.SENDER);
-        mNewMessageCount = Integer.parseInt(wrappedData.getString(OmtpConstants.NUM_MESSAGE_COUNT));
+        mSyncTriggerEvent = getString(wrappedData, OmtpConstants.SYNC_TRIGGER_EVENT);
+        mMessageId = getString(wrappedData, OmtpConstants.MESSAGE_UID);
+        mMessageLength = getInt(wrappedData, OmtpConstants.MESSAGE_LENGTH);
+        mContentType = getString(wrappedData, OmtpConstants.CONTENT_TYPE);
+        mSender = getString(wrappedData, OmtpConstants.SENDER);
+        mNewMessageCount = getInt(wrappedData, OmtpConstants.NUM_MESSAGE_COUNT);
         mMsgTimeMillis = parseTime(wrappedData.getString(OmtpConstants.TIME));
     }
 
-    static Long parseTime(String value) {
+    private static long parseTime(@Nullable String value) {
+        if (value == null) {
+            return 0L;
+        }
         try {
             return new SimpleDateFormat(
                     OmtpConstants.DATE_TIME_FORMAT, Locale.US)
@@ -92,8 +90,9 @@
     /**
      * @return the number of new messages stored on the voicemail server.
      */
+    @NeededForTesting
     public int getNewMessageCount() {
-        return mNewMessageCount != null ? mNewMessageCount : 0;
+        return mNewMessageCount;
     }
 
     /**
@@ -112,6 +111,7 @@
      * Expected to be set only for
      * {@link com.android.phone.vvm.omtp.OmtpConstants#NEW_MESSAGE}
      */
+    @NeededForTesting
     public String getContentType() {
         return mContentType;
     }
@@ -123,7 +123,7 @@
      * {@link com.android.phone.vvm.omtp.OmtpConstants#NEW_MESSAGE}
      */
     public int getLength() {
-        return mMessageLength != null ? mMessageLength : 0;
+        return mMessageLength;
     }
 
     /**
@@ -143,6 +143,26 @@
      * {@link com.android.phone.vvm.omtp.OmtpConstants#NEW_MESSAGE}
      */
     public long getTimestampMillis() {
-        return mMsgTimeMillis != null ? mMsgTimeMillis : 0;
+        return mMsgTimeMillis;
+    }
+
+    private static int getInt(Bundle wrappedData, String key) {
+        String value = wrappedData.getString(key);
+        if (value == null) {
+            return 0;
+        }
+        try {
+            return Integer.parseInt(value);
+        } catch (NumberFormatException e) {
+            return 0;
+        }
+    }
+
+    private static String getString(Bundle wrappedData, String key) {
+        String value = wrappedData.getString(key);
+        if (value == null) {
+            return "";
+        }
+        return value;
     }
 }
\ No newline at end of file
diff --git a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncReceiver.java b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncReceiver.java
index 0902b6d..415fc91 100644
--- a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncReceiver.java
+++ b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncReceiver.java
@@ -20,7 +20,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.provider.VoicemailContract;
-import android.util.Log;
+
+import com.android.phone.vvm.omtp.VvmLog;
 
 public class OmtpVvmSyncReceiver extends BroadcastReceiver {
 
@@ -29,7 +30,7 @@
     @Override
     public void onReceive(final Context context, Intent intent) {
         if (VoicemailContract.ACTION_SYNC_VOICEMAIL.equals(intent.getAction())) {
-            Log.v(TAG, "Sync intent received");
+            VvmLog.v(TAG, "Sync intent received");
             Intent syncIntent = OmtpVvmSyncService
                     .getSyncIntent(context, OmtpVvmSyncService.SYNC_FULL_SYNC, null, true);
             intent.putExtra(OmtpVvmSyncService.EXTRA_IS_MANUAL_SYNC, true);
diff --git a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
index c0411ec..74b1f66 100644
--- a/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
+++ b/src/com/android/phone/vvm/omtp/sync/OmtpVvmSyncService.java
@@ -27,14 +27,13 @@
 import android.telecom.PhoneAccountHandle;
 import android.telecom.Voicemail;
 import android.text.TextUtils;
-import android.util.Log;
 
 import com.android.phone.PhoneUtils;
 import com.android.phone.VoicemailStatus;
 import com.android.phone.settings.VisualVoicemailSettingsUtil;
-import com.android.phone.vvm.omtp.LocalLogHelper;
 import com.android.phone.vvm.omtp.OmtpEvents;
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
+import com.android.phone.vvm.omtp.VvmLog;
 import com.android.phone.vvm.omtp.fetch.VoicemailFetchedCallback;
 import com.android.phone.vvm.omtp.imap.ImapHelper;
 
@@ -169,21 +168,21 @@
     @Override
     protected void onHandleIntent(Intent intent) {
         if (intent == null) {
-            Log.d(TAG, "onHandleIntent: could not handle null intent");
+            VvmLog.d(TAG, "onHandleIntent: could not handle null intent");
             return;
         }
         String action = intent.getAction();
         PhoneAccountHandle phoneAccount = intent.getParcelableExtra(EXTRA_PHONE_ACCOUNT);
-        LocalLogHelper.log(TAG, "Sync requested: " + action +
+        VvmLog.log(TAG, "Sync requested: " + action +
                 " for all accounts: " + String.valueOf(phoneAccount == null));
 
         boolean isManualSync = intent.getBooleanExtra(EXTRA_IS_MANUAL_SYNC, false);
         Voicemail voicemail = intent.getParcelableExtra(EXTRA_VOICEMAIL);
         if (phoneAccount != null) {
-            Log.v(TAG, "Sync requested: " + action + " - for account: " + phoneAccount);
+            VvmLog.v(TAG, "Sync requested: " + action + " - for account: " + phoneAccount);
             setupAndSendRequest(phoneAccount, voicemail, action, isManualSync);
         } else {
-            Log.v(TAG, "Sync requested: " + action + " - for all accounts");
+            VvmLog.v(TAG, "Sync requested: " + action + " - for all accounts");
             OmtpVvmSourceManager vvmSourceManager =
                     OmtpVvmSourceManager.getInstance(this);
             Set<PhoneAccountHandle> sources = vvmSourceManager.getOmtpVvmSources();
@@ -196,7 +195,7 @@
     private void setupAndSendRequest(PhoneAccountHandle phoneAccount, Voicemail voicemail,
             String action, boolean isManualSync) {
         if (!VisualVoicemailSettingsUtil.isVisualVoicemailEnabled(this, phoneAccount)) {
-            Log.v(TAG, "Sync requested for disabled account");
+            VvmLog.v(TAG, "Sync requested for disabled account");
             return;
         }
 
@@ -208,7 +207,7 @@
                     : MINIMUM_MANUAL_SYNC_INTERVAL_MILLIS;
             if (currentTime - lastSyncTime < minimumInterval) {
                 // If it's been less than a minute since the last sync, bail.
-                Log.v(TAG, "Avoiding duplicate full sync: synced recently for "
+                VvmLog.v(TAG, "Avoiding duplicate full sync: synced recently for "
                         + phoneAccount.getId());
 
                 /**
@@ -238,7 +237,7 @@
             while (retryCount > 0) {
                 ImapHelper imapHelper = new ImapHelper(this, phoneAccount, network);
                 if (!imapHelper.isSuccessfullyInitialized()) {
-                    Log.w(TAG, "Can't retrieve Imap credentials.");
+                    VvmLog.w(TAG, "Can't retrieve Imap credentials.");
                     VisualVoicemailSettingsUtil.resetVisualVoicemailRetryInterval(this,
                             phoneAccount);
                     return;
@@ -257,7 +256,7 @@
                 if (VisualVoicemailSettingsUtil.isVisualVoicemailEnabled(this, phoneAccount) &&
                         !success) {
                     retryCount--;
-                    Log.v(TAG, "Retrying " + action);
+                    VvmLog.v(TAG, "Retrying " + action);
                 } else {
                     // Nothing more to do here, just exit.
                     VisualVoicemailSettingsUtil.resetVisualVoicemailRetryInterval(this,
@@ -285,7 +284,7 @@
             downloadSuccess = download(imapHelper, account);
         }
 
-        Log.v(TAG, "upload succeeded: [" + String.valueOf(uploadSuccess)
+        VvmLog.v(TAG, "upload succeeded: [" + String.valueOf(uploadSuccess)
                 + "] download succeeded: [" + String.valueOf(downloadSuccess) + "]");
 
         boolean success = uploadSuccess && downloadSuccess;
@@ -330,9 +329,9 @@
             super.onAvailable(network);
             NetworkInfo info = getConnectivityManager().getNetworkInfo(network);
             if (info == null) {
-                Log.d(TAG, "Network Type: Unknown");
+                VvmLog.d(TAG, "Network Type: Unknown");
             } else {
-                Log.d(TAG, "Network Type: " + info.getTypeName());
+                VvmLog.d(TAG, "Network Type: " + info.getTypeName());
             }
 
             doSync(network, this, mPhoneAccount, mVoicemail, mAction);
@@ -428,7 +427,7 @@
         long retryInterval = VisualVoicemailSettingsUtil.getVisualVoicemailRetryInterval(this,
                 phoneAccount);
 
-        Log.v(TAG, "Retrying " + action + " in " + retryInterval + "ms");
+        VvmLog.v(TAG, "Retrying " + action + " in " + retryInterval + "ms");
 
         AlarmManager alarmManager = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
         alarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + retryInterval,
diff --git a/src/com/android/phone/vvm/omtp/sync/VvmNetworkRequestCallback.java b/src/com/android/phone/vvm/omtp/sync/VvmNetworkRequestCallback.java
index 4e09527..11526ce 100644
--- a/src/com/android/phone/vvm/omtp/sync/VvmNetworkRequestCallback.java
+++ b/src/com/android/phone/vvm/omtp/sync/VvmNetworkRequestCallback.java
@@ -24,11 +24,11 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.telecom.PhoneAccountHandle;
-import android.util.Log;
 
 import com.android.phone.PhoneUtils;
 import com.android.phone.vvm.omtp.OmtpEvents;
 import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
+import com.android.phone.vvm.omtp.VvmLog;
 
 /**
  * Base class for network request call backs for visual voicemail syncing with the Imap server. This
@@ -80,11 +80,11 @@
                 .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
 
         if (mCarrierConfigHelper.isCellularDataRequired()) {
-            Log.d(TAG, "Transport type: CELLULAR");
+            VvmLog.d(TAG, "Transport type: CELLULAR");
             builder.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                     .setNetworkSpecifier(Integer.toString(mSubId));
         } else {
-            Log.d(TAG, "Transport type: ANY");
+            VvmLog.d(TAG, "Transport type: ANY");
         }
         return builder.build();
     }
@@ -96,7 +96,7 @@
     @Override
     @CallSuper
     public void onLost(Network network) {
-        Log.d(TAG, "onLost");
+        VvmLog.d(TAG, "onLost");
         mResultReceived = true;
         onFailed(NETWORK_REQUEST_FAILED_LOST);
     }
@@ -117,7 +117,7 @@
 
     public void requestNetwork() {
         if (mRequestSent == true) {
-            Log.e(TAG, "requestNetwork() called twice");
+            VvmLog.e(TAG, "requestNetwork() called twice");
             return;
         }
         mRequestSent = true;
@@ -138,7 +138,7 @@
     }
 
     public void releaseNetwork() {
-        Log.d(TAG, "releaseNetwork");
+        VvmLog.d(TAG, "releaseNetwork");
         getConnectivityManager().unregisterNetworkCallback(this);
     }
 
@@ -152,7 +152,7 @@
 
     @CallSuper
     public void onFailed(String reason) {
-        Log.d(TAG, "onFailed: " + reason);
+        VvmLog.d(TAG, "onFailed: " + reason);
         if (mCarrierConfigHelper.isCellularDataRequired()) {
             mCarrierConfigHelper.handleEvent(OmtpEvents.DATA_NO_CONNECTION_CELLULAR_REQUIRED);
         } else {
diff --git a/src/com/android/phone/vvm/omtp/utils/VvmDumpHandler.java b/src/com/android/phone/vvm/omtp/utils/VvmDumpHandler.java
new file mode 100644
index 0000000..227cf42
--- /dev/null
+++ b/src/com/android/phone/vvm/omtp/utils/VvmDumpHandler.java
@@ -0,0 +1,32 @@
+package com.android.phone.vvm.omtp.utils;
+
+import android.content.Context;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.phone.vvm.omtp.OmtpVvmCarrierConfigHelper;
+import com.android.phone.vvm.omtp.VvmLog;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+public class VvmDumpHandler {
+
+    public static void dump(Context context, FileDescriptor fd, PrintWriter writer,
+            String[] args) {
+        IndentingPrintWriter indentedWriter = new IndentingPrintWriter(writer, "  ");
+        indentedWriter.println("******* OmtpVvm *******");
+        indentedWriter.println("======= Configs =======");
+        indentedWriter.increaseIndent();
+        for (PhoneAccountHandle handle : TelecomManager.from(context)
+                .getCallCapablePhoneAccounts()) {
+            int subId = PhoneAccountHandleConverter.toSubId(handle);
+            OmtpVvmCarrierConfigHelper config = new OmtpVvmCarrierConfigHelper(context, subId);
+            indentedWriter.println(config.toString());
+        }
+        indentedWriter.decreaseIndent();
+        indentedWriter.println("======== Logs =========");
+        VvmLog.dump(fd, indentedWriter, args);
+    }
+}
diff --git a/src/com/android/services/telephony/EmergencyCallHelper.java b/src/com/android/services/telephony/EmergencyCallHelper.java
index c64a649..295f4f7 100644
--- a/src/com/android/services/telephony/EmergencyCallHelper.java
+++ b/src/com/android/services/telephony/EmergencyCallHelper.java
@@ -17,18 +17,17 @@
 package com.android.services.telephony;
 
 import android.content.Context;
-
 import android.content.Intent;
-import android.os.AsyncResult;
-import android.os.Handler;
-import android.os.Message;
 import android.os.UserHandle;
 import android.provider.Settings;
-import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
 
-import com.android.internal.os.SomeArgs;
 import com.android.internal.telephony.Phone;
-import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneFactory;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
 
 /**
  * Helper class that implements special behavior related to emergency calls. Specifically, this
@@ -36,220 +35,75 @@
  * (i.e. the device is in airplane mode), by forcibly turning the radio back on, waiting for it to
  * come up, and then retrying the emergency call.
  */
-public class EmergencyCallHelper {
-
-    /**
-     * Receives the result of the EmergencyCallHelper's attempt to turn on the radio.
-     */
-    interface Callback {
-        void onComplete(boolean isRadioReady);
-    }
-
-    // Number of times to retry the call, and time between retry attempts.
-    public static final int MAX_NUM_RETRIES = 5;
-    public static final long TIME_BETWEEN_RETRIES_MILLIS = 5000;  // msec
-
-    // Handler message codes; see handleMessage()
-    private static final int MSG_START_SEQUENCE = 1;
-    private static final int MSG_SERVICE_STATE_CHANGED = 2;
-    private static final int MSG_RETRY_TIMEOUT = 3;
+public class EmergencyCallHelper implements EmergencyCallStateListener.Callback {
 
     private final Context mContext;
+    private EmergencyCallStateListener.Callback mCallback;
+    private List<EmergencyCallStateListener> mListeners;
+    private List<EmergencyCallStateListener> mInProgressListeners;
+    private boolean mIsEmergencyCallingEnabled;
 
-    private final Handler mHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_START_SEQUENCE:
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    Phone phone = (Phone) args.arg1;
-                    EmergencyCallHelper.Callback callback =
-                            (EmergencyCallHelper.Callback) args.arg2;
-                    args.recycle();
-
-                    startSequenceInternal(phone, callback);
-                    break;
-                case MSG_SERVICE_STATE_CHANGED:
-                    onServiceStateChanged((ServiceState) ((AsyncResult) msg.obj).result);
-                    break;
-                case MSG_RETRY_TIMEOUT:
-                    onRetryTimeout();
-                    break;
-                default:
-                    Log.wtf(this, "handleMessage: unexpected message: %d.", msg.what);
-                    break;
-            }
-        }
-    };
-
-
-    private Callback mCallback;  // The callback to notify upon completion.
-    private Phone mPhone;  // The phone that will attempt to place the call.
-    private int mNumRetriesSoFar;
 
     public EmergencyCallHelper(Context context) {
-        Log.d(this, "EmergencyCallHelper constructor.");
         mContext = context;
+        mInProgressListeners = new ArrayList<>(2);
     }
 
+    private void setupListeners() {
+        if (mListeners != null) {
+            return;
+        }
+        mListeners = new ArrayList<>(2);
+        for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) {
+            mListeners.add(new EmergencyCallStateListener());
+        }
+    }
     /**
      * Starts the "turn on radio" sequence. This is the (single) external API of the
      * EmergencyCallHelper class.
      *
      * This method kicks off the following sequence:
-     * - Power on the radio.
+     * - Power on the radio for each Phone
      * - Listen for the service state change event telling us the radio has come up.
-     * - Retry if we've gone {@link #TIME_BETWEEN_RETRIES_MILLIS} without any response from the
-     *   radio.
+     * - Retry if we've gone a significant amount of time without any response from the radio.
      * - Finally, clean up any leftover state.
      *
      * This method is safe to call from any thread, since it simply posts a message to the
      * EmergencyCallHelper's handler (thus ensuring that the rest of the sequence is entirely
-     * serialized, and runs only on the handler thread.)
+     * serialized, and runs on the main looper.)
      */
-    public void startTurnOnRadioSequence(Phone phone, Callback callback) {
-        Log.d(this, "startTurnOnRadioSequence");
-
-        SomeArgs args = SomeArgs.obtain();
-        args.arg1 = phone;
-        args.arg2 = callback;
-        mHandler.obtainMessage(MSG_START_SEQUENCE, args).sendToTarget();
-    }
-
-    /**
-     * Actual implementation of startTurnOnRadioSequence(), guaranteed to run on the handler thread.
-     * @see #startTurnOnRadioSequence
-     */
-    private void startSequenceInternal(Phone phone, Callback callback) {
-        Log.d(this, "startSequenceInternal()");
-
-        // First of all, clean up any state left over from a prior emergency call sequence. This
-        // ensures that we'll behave sanely if another startTurnOnRadioSequence() comes in while
-        // we're already in the middle of the sequence.
-        cleanup();
-
-        mPhone = phone;
+    public void enableEmergencyCalling(EmergencyCallStateListener.Callback callback) {
+        setupListeners();
         mCallback = callback;
+        mInProgressListeners.clear();
+        mIsEmergencyCallingEnabled = false;
+        for (int i = 0; i < TelephonyManager.getDefault().getPhoneCount(); i++) {
+            Phone phone = PhoneFactory.getPhone(i);
+            if (phone == null)
+                continue;
 
-
-        // No need to check the current service state here, since the only reason to invoke this
-        // method in the first place is if the radio is powered-off. So just go ahead and turn the
-        // radio on.
-
-        powerOnRadio();  // We'll get an onServiceStateChanged() callback
-                         // when the radio successfully comes up.
-
-        // Next step: when the SERVICE_STATE_CHANGED event comes in, we'll retry the call; see
-        // onServiceStateChanged(). But also, just in case, start a timer to make sure we'll retry
-        // the call even if the SERVICE_STATE_CHANGED event never comes in for some reason.
-        startRetryTimer();
-    }
-
-    /**
-     * Handles the SERVICE_STATE_CHANGED event. Normally this event tells us that the radio has
-     * finally come up. In that case, it's now safe to actually place the emergency call.
-     */
-    private void onServiceStateChanged(ServiceState state) {
-        Log.d(this, "onServiceStateChanged(), new state = %s.", state);
-
-        // Possible service states:
-        // - STATE_IN_SERVICE        // Normal operation
-        // - STATE_OUT_OF_SERVICE    // Still searching for an operator to register to,
-        //                           // or no radio signal
-        // - STATE_EMERGENCY_ONLY    // Phone is locked; only emergency numbers are allowed
-        // - STATE_POWER_OFF         // Radio is explicitly powered off (airplane mode)
-
-        if (isOkToCall(state.getState(), mPhone.getState())) {
-            // Woo hoo!  It's OK to actually place the call.
-            Log.d(this, "onServiceStateChanged: ok to call!");
-
-            onComplete(true);
-            cleanup();
-        } else {
-            // The service state changed, but we're still not ready to call yet. (This probably was
-            // the transition from STATE_POWER_OFF to STATE_OUT_OF_SERVICE, which happens
-            // immediately after powering-on the radio.)
-            //
-            // So just keep waiting; we'll probably get to either STATE_IN_SERVICE or
-            // STATE_EMERGENCY_ONLY very shortly. (Or even if that doesn't happen, we'll at least do
-            // another retry when the RETRY_TIMEOUT event fires.)
-            Log.d(this, "onServiceStateChanged: not ready to call yet, keep waiting.");
+            mInProgressListeners.add(mListeners.get(i));
+            mListeners.get(i).waitForRadioOn(phone, this);
         }
+
+        powerOnRadio();
     }
-
-    private boolean isOkToCall(int serviceState, PhoneConstants.State phoneState) {
-        // Once we reach either STATE_IN_SERVICE or STATE_EMERGENCY_ONLY, it's finally OK to place
-        // the emergency call.
-        return ((phoneState == PhoneConstants.State.OFFHOOK)
-                || (serviceState == ServiceState.STATE_IN_SERVICE)
-                || (serviceState == ServiceState.STATE_EMERGENCY_ONLY)) ||
-
-                // Allow STATE_OUT_OF_SERVICE if we are at the max number of retries.
-                (mNumRetriesSoFar == MAX_NUM_RETRIES &&
-                 serviceState == ServiceState.STATE_OUT_OF_SERVICE);
-    }
-
     /**
-     * Handles the retry timer expiring.
-     */
-    private void onRetryTimeout() {
-        PhoneConstants.State phoneState = mPhone.getState();
-        int serviceState = mPhone.getServiceState().getState();
-        Log.d(this, "onRetryTimeout():  phone state = %s, service state = %d, retries = %d.",
-               phoneState, serviceState, mNumRetriesSoFar);
-
-        // - If we're actually in a call, we've succeeded.
-        // - Otherwise, if the radio is now on, that means we successfully got out of airplane mode
-        //   but somehow didn't get the service state change event.  In that case, try to place the
-        //   call.
-        // - If the radio is still powered off, try powering it on again.
-
-        if (isOkToCall(serviceState, phoneState)) {
-            Log.d(this, "onRetryTimeout: Radio is on. Cleaning up.");
-
-            // Woo hoo -- we successfully got out of airplane mode.
-            onComplete(true);
-            cleanup();
-        } else {
-            // Uh oh; we've waited the full TIME_BETWEEN_RETRIES_MILLIS and the radio is still not
-            // powered-on.  Try again.
-
-            mNumRetriesSoFar++;
-            Log.d(this, "mNumRetriesSoFar is now " + mNumRetriesSoFar);
-
-            if (mNumRetriesSoFar > MAX_NUM_RETRIES) {
-                Log.w(this, "Hit MAX_NUM_RETRIES; giving up.");
-                cleanup();
-            } else {
-                Log.d(this, "Trying (again) to turn on the radio.");
-                powerOnRadio();  // Again, we'll (hopefully) get an onServiceStateChanged() callback
-                                 // when the radio successfully comes up.
-                startRetryTimer();
-            }
-        }
-    }
-
-    /**
-     * Attempt to power on the radio (i.e. take the device out of airplane mode.)
-     * Additionally, start listening for service state changes; we'll eventually get an
-     * onServiceStateChanged() callback when the radio successfully comes up.
+     * Attempt to power on the radio (i.e. take the device out of airplane mode). We'll eventually
+     * get an onServiceStateChanged() callback when the radio successfully comes up.
      */
     private void powerOnRadio() {
         Log.d(this, "powerOnRadio().");
 
-        // We're about to turn on the radio, so arrange to be notified when the sequence is
-        // complete.
-        registerForServiceStateChanged();
-
         // If airplane mode is on, we turn it off the same way that the Settings activity turns it
         // off.
         if (Settings.Global.getInt(mContext.getContentResolver(),
-                                   Settings.Global.AIRPLANE_MODE_ON, 0) > 0) {
+                Settings.Global.AIRPLANE_MODE_ON, 0) > 0) {
             Log.d(this, "==> Turning off airplane mode.");
 
             // Change the system setting
             Settings.Global.putInt(mContext.getContentResolver(),
-                                   Settings.Global.AIRPLANE_MODE_ON, 0);
+                    Settings.Global.AIRPLANE_MODE_ON, 0);
 
             // Post the broadcast intend for change in airplane mode
             // TODO: We really should not be in charge of sending this broadcast.
@@ -258,77 +112,19 @@
             Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
             intent.putExtra("state", false);
             mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
-        } else {
-            // Otherwise, for some strange reason the radio is off (even though the Settings
-            // database doesn't think we're in airplane mode.)  In this case just turn the radio
-            // back on.
-            Log.d(this, "==> (Apparently) not in airplane mode; manually powering radio on.");
-            mPhone.setRadioPower(true);
         }
     }
 
     /**
-     * Clean up when done with the whole sequence: either after successfully turning on the radio,
-     * or after bailing out because of too many failures.
-     *
-     * The exact cleanup steps are:
-     * - Notify callback if we still hadn't sent it a response.
-     * - Double-check that we're not still registered for any telephony events
-     * - Clean up any extraneous handler messages (like retry timeouts) still in the queue
-     *
-     * Basically this method guarantees that there will be no more activity from the
-     * EmergencyCallHelper until someone kicks off the whole sequence again with another call to
-     * {@link #startTurnOnRadioSequence}
-     *
-     * TODO: Do the work for the comment below:
-     * Note we don't call this method simply after a successful call to placeCall(), since it's
-     * still possible the call will disconnect very quickly with an OUT_OF_SERVICE error.
+     * This method is called from multiple Listeners on the Main Looper.
+     * Synchronization is not necessary.
      */
-    private void cleanup() {
-        Log.d(this, "cleanup()");
-
-        // This will send a failure call back if callback has yet to be invoked.  If the callback
-        // was already invoked, it's a no-op.
-        onComplete(false);
-
-        unregisterForServiceStateChanged();
-        cancelRetryTimer();
-
-        // Used for unregisterForServiceStateChanged() so we null it out here instead.
-        mPhone = null;
-        mNumRetriesSoFar = 0;
-    }
-
-    private void startRetryTimer() {
-        cancelRetryTimer();
-        mHandler.sendEmptyMessageDelayed(MSG_RETRY_TIMEOUT, TIME_BETWEEN_RETRIES_MILLIS);
-    }
-
-    private void cancelRetryTimer() {
-        mHandler.removeMessages(MSG_RETRY_TIMEOUT);
-    }
-
-    private void registerForServiceStateChanged() {
-        // Unregister first, just to make sure we never register ourselves twice.  (We need this
-        // because Phone.registerForServiceStateChanged() does not prevent multiple registration of
-        // the same handler.)
-        unregisterForServiceStateChanged();
-        mPhone.registerForServiceStateChanged(mHandler, MSG_SERVICE_STATE_CHANGED, null);
-    }
-
-    private void unregisterForServiceStateChanged() {
-        // This method is safe to call even if we haven't set mPhone yet.
-        if (mPhone != null) {
-            mPhone.unregisterForServiceStateChanged(mHandler);  // Safe even if unnecessary
-        }
-        mHandler.removeMessages(MSG_SERVICE_STATE_CHANGED);  // Clean up any pending messages too
-    }
-
-    private void onComplete(boolean isRadioReady) {
-        if (mCallback != null) {
-            Callback tempCallback = mCallback;
-            mCallback = null;
-            tempCallback.onComplete(isRadioReady);
+    @Override
+    public void onComplete(EmergencyCallStateListener listener, boolean isRadioReady) {
+        mIsEmergencyCallingEnabled |= isRadioReady;
+        mInProgressListeners.remove(listener);
+        if (mCallback != null && mInProgressListeners.isEmpty()) {
+            mCallback.onComplete(null, mIsEmergencyCallingEnabled);
         }
     }
 }
diff --git a/src/com/android/services/telephony/EmergencyCallStateListener.java b/src/com/android/services/telephony/EmergencyCallStateListener.java
new file mode 100644
index 0000000..2346a7f
--- /dev/null
+++ b/src/com/android/services/telephony/EmergencyCallStateListener.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.services.telephony;
+
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.telephony.ServiceState;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.SubscriptionController;
+
+/**
+ * Helper class that listens to a Phone's radio state and sends a callback when the radio state of
+ * that Phone is either "in service" or "emergency calls only."
+ */
+public class EmergencyCallStateListener {
+
+    /**
+     * Receives the result of the EmergencyCallStateListener's attempt to turn on the radio.
+     */
+    interface Callback {
+        void onComplete(EmergencyCallStateListener listener, boolean isRadioReady);
+    }
+
+    // Number of times to retry the call, and time between retry attempts.
+    private static int MAX_NUM_RETRIES = 5;
+    private static long TIME_BETWEEN_RETRIES_MILLIS = 5000;  // msec
+
+    // Handler message codes; see handleMessage()
+    @VisibleForTesting
+    public static final int MSG_START_SEQUENCE = 1;
+    @VisibleForTesting
+    public static final int MSG_SERVICE_STATE_CHANGED = 2;
+    @VisibleForTesting
+    public static final int MSG_RETRY_TIMEOUT = 3;
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_START_SEQUENCE:
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    try {
+                        Phone phone = (Phone) args.arg1;
+                        EmergencyCallStateListener.Callback callback =
+                                (EmergencyCallStateListener.Callback) args.arg2;
+                        startSequenceInternal(phone, callback);
+                    } finally {
+                        args.recycle();
+                    }
+                    break;
+                case MSG_SERVICE_STATE_CHANGED:
+                    onServiceStateChanged((ServiceState) ((AsyncResult) msg.obj).result);
+                    break;
+                case MSG_RETRY_TIMEOUT:
+                    onRetryTimeout();
+                    break;
+                default:
+                    Log.wtf(this, "handleMessage: unexpected message: %d.", msg.what);
+                    break;
+            }
+        }
+    };
+
+
+    private Callback mCallback;  // The callback to notify upon completion.
+    private Phone mPhone;  // The phone that will attempt to place the call.
+    private int mNumRetriesSoFar;
+
+    /**
+     * Starts the "wait for radio" sequence. This is the (single) external API of the
+     * EmergencyCallStateListener class.
+     *
+     * This method kicks off the following sequence:
+     * - Listen for the service state change event telling us the radio has come up.
+     * - Retry if we've gone {@link #TIME_BETWEEN_RETRIES_MILLIS} without any response from the
+     *   radio.
+     * - Finally, clean up any leftover state.
+     *
+     * This method is safe to call from any thread, since it simply posts a message to the
+     * EmergencyCallStateListener's handler (thus ensuring that the rest of the sequence is entirely
+     * serialized, and runs only on the handler thread.)
+     */
+    public void waitForRadioOn(Phone phone, Callback callback) {
+        Log.d(this, "waitForRadioOn: Phone " + phone.getPhoneId());
+
+        if (mPhone != null) {
+            // If there already is an ongoing request, ignore the new one!
+            return;
+        }
+
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = phone;
+        args.arg2 = callback;
+        mHandler.obtainMessage(MSG_START_SEQUENCE, args).sendToTarget();
+    }
+
+    /**
+     * Actual implementation of waitForRadioOn(), guaranteed to run on the handler thread.
+     *
+     * @see #waitForRadioOn
+     */
+    private void startSequenceInternal(Phone phone, Callback callback) {
+        Log.d(this, "startSequenceInternal: Phone " + phone.getPhoneId());
+
+        // First of all, clean up any state left over from a prior emergency call sequence. This
+        // ensures that we'll behave sanely if another startTurnOnRadioSequence() comes in while
+        // we're already in the middle of the sequence.
+        cleanup();
+
+        mPhone = phone;
+        mCallback = callback;
+
+        registerForServiceStateChanged();
+        // Next step: when the SERVICE_STATE_CHANGED event comes in, we'll retry the call; see
+        // onServiceStateChanged(). But also, just in case, start a timer to make sure we'll retry
+        // the call even if the SERVICE_STATE_CHANGED event never comes in for some reason.
+        startRetryTimer();
+    }
+
+    /**
+     * Handles the SERVICE_STATE_CHANGED event. Normally this event tells us that the radio has
+     * finally come up. In that case, it's now safe to actually place the emergency call.
+     */
+    private void onServiceStateChanged(ServiceState state) {
+        Log.d(this, "onServiceStateChanged(), new state = %s, Phone = %s", state,
+                mPhone.getPhoneId());
+
+        // Possible service states:
+        // - STATE_IN_SERVICE        // Normal operation
+        // - STATE_OUT_OF_SERVICE    // Still searching for an operator to register to,
+        //                           // or no radio signal
+        // - STATE_EMERGENCY_ONLY    // Phone is locked; only emergency numbers are allowed
+        // - STATE_POWER_OFF         // Radio is explicitly powered off (airplane mode)
+
+        if (isOkToCall(state.getState())) {
+            // Woo hoo!  It's OK to actually place the call.
+            Log.d(this, "onServiceStateChanged: ok to call!");
+
+            onComplete(true);
+            cleanup();
+        } else {
+            // The service state changed, but we're still not ready to call yet. (This probably was
+            // the transition from STATE_POWER_OFF to STATE_OUT_OF_SERVICE, which happens
+            // immediately after powering-on the radio.)
+            //
+            // So just keep waiting; we'll probably get to either STATE_IN_SERVICE or
+            // STATE_EMERGENCY_ONLY very shortly. (Or even if that doesn't happen, we'll at least do
+            // another retry when the RETRY_TIMEOUT event fires.)
+            Log.d(this, "onServiceStateChanged: not ready to call yet, keep waiting.");
+        }
+    }
+
+    private boolean isOkToCall(int serviceState) {
+        // Once we reach either STATE_IN_SERVICE or STATE_EMERGENCY_ONLY, it's finally OK to place
+        // the emergency call.
+        return ((mPhone.getState() == PhoneConstants.State.OFFHOOK)
+                || (serviceState == ServiceState.STATE_IN_SERVICE)
+                || (serviceState == ServiceState.STATE_EMERGENCY_ONLY))
+                // STATE_EMERGENCY_ONLY currently is not used, so we must also check the service
+                // state for emergency only calling.
+                || (serviceState == ServiceState.STATE_OUT_OF_SERVICE &&
+                        mPhone.getServiceState().isEmergencyOnly())
+                // Allow STATE_OUT_OF_SERVICE if we are at the max number of retries.
+                || (mNumRetriesSoFar == MAX_NUM_RETRIES &&
+                        serviceState == ServiceState.STATE_OUT_OF_SERVICE);
+    }
+
+    /**
+     * Handles the retry timer expiring.
+     */
+    private void onRetryTimeout() {
+        int serviceState = mPhone.getServiceState().getState();
+        Log.d(this, "onRetryTimeout():  phone state = %s, service state = %d, retries = %d.",
+                mPhone.getState(), serviceState, mNumRetriesSoFar);
+
+        // - If we're actually in a call, we've succeeded.
+        // - Otherwise, if the radio is now on, that means we successfully got out of airplane mode
+        //   but somehow didn't get the service state change event.  In that case, try to place the
+        //   call.
+        // - If the radio is still powered off, try powering it on again.
+
+        if (isOkToCall(serviceState)) {
+            Log.d(this, "onRetryTimeout: Radio is on. Cleaning up.");
+
+            // Woo hoo -- we successfully got out of airplane mode.
+            onComplete(true);
+            cleanup();
+        } else {
+            // Uh oh; we've waited the full TIME_BETWEEN_RETRIES_MILLIS and the radio is still not
+            // powered-on.  Try again.
+
+            mNumRetriesSoFar++;
+            Log.d(this, "mNumRetriesSoFar is now " + mNumRetriesSoFar);
+
+            if (mNumRetriesSoFar > MAX_NUM_RETRIES) {
+                Log.w(this, "Hit MAX_NUM_RETRIES; giving up.");
+                cleanup();
+            } else {
+                Log.d(this, "Trying (again) to turn on the radio.");
+                mPhone.setRadioPower(true);
+                startRetryTimer();
+            }
+        }
+    }
+
+    /**
+     * Clean up when done with the whole sequence: either after successfully turning on the radio,
+     * or after bailing out because of too many failures.
+     *
+     * The exact cleanup steps are:
+     * - Notify callback if we still hadn't sent it a response.
+     * - Double-check that we're not still registered for any telephony events
+     * - Clean up any extraneous handler messages (like retry timeouts) still in the queue
+     *
+     * Basically this method guarantees that there will be no more activity from the
+     * EmergencyCallStateListener until someone kicks off the whole sequence again with another call
+     * to {@link #waitForRadioOn}
+     *
+     * TODO: Do the work for the comment below:
+     * Note we don't call this method simply after a successful call to placeCall(), since it's
+     * still possible the call will disconnect very quickly with an OUT_OF_SERVICE error.
+     */
+    private void cleanup() {
+        Log.d(this, "cleanup()");
+
+        // This will send a failure call back if callback has yet to be invoked.  If the callback
+        // was already invoked, it's a no-op.
+        onComplete(false);
+
+        unregisterForServiceStateChanged();
+        cancelRetryTimer();
+
+        // Used for unregisterForServiceStateChanged() so we null it out here instead.
+        mPhone = null;
+        mNumRetriesSoFar = 0;
+    }
+
+    private void startRetryTimer() {
+        cancelRetryTimer();
+        mHandler.sendEmptyMessageDelayed(MSG_RETRY_TIMEOUT, TIME_BETWEEN_RETRIES_MILLIS);
+    }
+
+    private void cancelRetryTimer() {
+        mHandler.removeMessages(MSG_RETRY_TIMEOUT);
+    }
+
+    private void registerForServiceStateChanged() {
+        // Unregister first, just to make sure we never register ourselves twice.  (We need this
+        // because Phone.registerForServiceStateChanged() does not prevent multiple registration of
+        // the same handler.)
+        unregisterForServiceStateChanged();
+        mPhone.registerForServiceStateChanged(mHandler, MSG_SERVICE_STATE_CHANGED, null);
+    }
+
+    private void unregisterForServiceStateChanged() {
+        // This method is safe to call even if we haven't set mPhone yet.
+        if (mPhone != null) {
+            mPhone.unregisterForServiceStateChanged(mHandler);  // Safe even if unnecessary
+        }
+        mHandler.removeMessages(MSG_SERVICE_STATE_CHANGED);  // Clean up any pending messages too
+    }
+
+    private void onComplete(boolean isRadioReady) {
+        if (mCallback != null) {
+            Callback tempCallback = mCallback;
+            mCallback = null;
+            tempCallback.onComplete(this, isRadioReady);
+        }
+    }
+
+    @VisibleForTesting
+    public Handler getHandler() {
+        return mHandler;
+    }
+
+    @VisibleForTesting
+    public void setMaxNumRetries(int retries) {
+        MAX_NUM_RETRIES = retries;
+    }
+
+    @VisibleForTesting
+    public void setTimeBetweenRetriesMillis(long timeMs) {
+        TIME_BETWEEN_RETRIES_MILLIS = timeMs;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || !getClass().equals(o.getClass())) return false;
+
+        EmergencyCallStateListener that = (EmergencyCallStateListener) o;
+
+        if (mNumRetriesSoFar != that.mNumRetriesSoFar) {
+            return false;
+        }
+        if (mCallback != null ? !mCallback.equals(that.mCallback) : that.mCallback != null) {
+            return false;
+        }
+        return mPhone != null ? mPhone.equals(that.mPhone) : that.mPhone == null;
+
+    }
+}
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 69d57d7..62bbfe2 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -808,13 +808,19 @@
             }
 
             PhoneAccountHandle phoneAccountHandle = null;
-            if (mConferenceHost.getPhone() != null &&
-                    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).
-                phoneAccountHandle =
-                        PhoneUtils.makePstnPhoneAccountHandle(imsPhone.getDefaultPhone());
+            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).
+                    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.
+                    phoneAccountHandle = PhoneUtils.makePstnPhoneAccountHandle(
+                            mConferenceHost.getPhone());
+                }
             }
 
             if (mConferenceHost.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
diff --git a/src/com/android/services/telephony/ImsConferenceController.java b/src/com/android/services/telephony/ImsConferenceController.java
index 0f9ae5d..a874674 100644
--- a/src/com/android/services/telephony/ImsConferenceController.java
+++ b/src/com/android/services/telephony/ImsConferenceController.java
@@ -78,6 +78,12 @@
             Log.v(this, "onConferenceStarted");
             recalculate();
         }
+
+        @Override
+        public void onConferenceSupportedChanged(Connection c, boolean isConferenceSupported) {
+            Log.v(this, "onConferenceSupportedChanged");
+            recalculate();
+        }
     };
 
     /**
@@ -172,6 +178,7 @@
             // If this connection does not support being in a conference call, then it is not
             // conferenceable with any other connection.
             if (!connection.isConferenceSupported()) {
+                connection.setConferenceableConnections(Collections.<Connection>emptyList());
                 continue;
             }
 
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index 19b1d8a..b5b23b4 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -27,7 +27,6 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.PersistableBundle;
-import android.os.ServiceManager;
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
@@ -40,7 +39,6 @@
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 
-import com.android.internal.telephony.IPhoneSubInfo;
 import com.android.internal.telephony.Phone;
 import com.android.internal.telephony.PhoneFactory;
 import com.android.phone.PhoneGlobals;
@@ -50,6 +48,7 @@
 import java.util.Arrays;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Optional;
 
 /**
  * Owns all data we have registered with Telecom including handling dynamic addition and
@@ -72,6 +71,7 @@
         private boolean mIsVideoPauseSupported;
         private boolean mIsMergeCallSupported;
         private boolean mIsVideoConferencingSupported;
+        private boolean mIsMergeOfWifiCallsAllowedWhenVoWifiOff;
 
         AccountEntry(Phone phone, boolean isEmergency, boolean isDummy) {
             mPhone = phone;
@@ -191,6 +191,8 @@
             }
             mIsMergeCallSupported = isCarrierMergeCallSupported();
             mIsVideoConferencingSupported = isCarrierVideoConferencingSupported();
+            mIsMergeOfWifiCallsAllowedWhenVoWifiOff =
+                    isCarrierMergeOfWifiCallsAllowedWhenVoWifiOff();
 
             if (isEmergency && mContext.getResources().getBoolean(
                     R.bool.config_emergency_account_emergency_calls_only)) {
@@ -312,6 +314,20 @@
         }
 
         /**
+         * Determines from carrier config whether merging of wifi calls is allowed when VoWIFI is
+         * turned off.
+         *
+         * @return {@code true} merging of wifi calls when VoWIFI is disabled should be prevented,
+         *      {@code false} otherwise.
+         */
+        private boolean isCarrierMergeOfWifiCallsAllowedWhenVoWifiOff() {
+            PersistableBundle b =
+                    PhoneGlobals.getInstance().getCarrierConfigForSubId(mPhone.getSubId());
+            return b != null && b.getBoolean(
+                    CarrierConfigManager.KEY_ALLOW_MERGE_WIFI_CALLS_WHEN_VOWIFI_OFF_BOOL);
+        }
+
+        /**
          * @return The {@linke PhoneAccount} extras associated with the current subscription.
          */
         private Bundle getPhoneAccountExtras() {
@@ -366,6 +382,14 @@
         public boolean isVideoConferencingSupported() {
             return mIsVideoConferencingSupported;
         }
+
+        /**
+         * Indicate whether this account allow merging of wifi calls when VoWIFI is off.
+         * @return {@code true} if allowed, {@code false} otherwise.
+         */
+        public boolean isMergeOfWifiCallsAllowedWhenVoWifiOff() {
+            return mIsMergeOfWifiCallsAllowedWhenVoWifiOff;
+        }
     }
 
     private OnSubscriptionsChangedListener mOnSubscriptionsChangedListener =
@@ -396,6 +420,7 @@
     private final TelephonyManager mTelephonyManager;
     private final SubscriptionManager mSubscriptionManager;
     private List<AccountEntry> mAccounts = new LinkedList<AccountEntry>();
+    private Object mAccountsLock = new Object();
     private int mServiceState = ServiceState.STATE_POWER_OFF;
 
     // TODO: Remove back-pointer from app singleton to Service, since this is not a preferred
@@ -432,9 +457,11 @@
      * @return {@code True} if video pausing is supported.
      */
     boolean isVideoPauseSupported(PhoneAccountHandle handle) {
-        for (AccountEntry entry : mAccounts) {
-            if (entry.getPhoneAccountHandle().equals(handle)) {
-                return entry.isVideoPauseSupported();
+        synchronized (mAccountsLock) {
+            for (AccountEntry entry : mAccounts) {
+                if (entry.getPhoneAccountHandle().equals(handle)) {
+                    return entry.isVideoPauseSupported();
+                }
             }
         }
         return false;
@@ -448,9 +475,11 @@
      * @return {@code True} if merging calls is supported.
      */
     boolean isMergeCallSupported(PhoneAccountHandle handle) {
-        for (AccountEntry entry : mAccounts) {
-            if (entry.getPhoneAccountHandle().equals(handle)) {
-                return entry.isMergeCallSupported();
+        synchronized (mAccountsLock) {
+            for (AccountEntry entry : mAccounts) {
+                if (entry.getPhoneAccountHandle().equals(handle)) {
+                    return entry.isMergeCallSupported();
+                }
             }
         }
         return false;
@@ -464,15 +493,37 @@
      * @return {@code True} if video conferencing is supported.
      */
     boolean isVideoConferencingSupported(PhoneAccountHandle handle) {
-        for (AccountEntry entry : mAccounts) {
-            if (entry.getPhoneAccountHandle().equals(handle)) {
-                return entry.isVideoConferencingSupported();
+        synchronized (mAccountsLock) {
+            for (AccountEntry entry : mAccounts) {
+                if (entry.getPhoneAccountHandle().equals(handle)) {
+                    return entry.isVideoConferencingSupported();
+                }
             }
         }
         return false;
     }
 
     /**
+     * Determines if the {@link AccountEntry} associated with a {@link PhoneAccountHandle} allows
+     * merging of wifi calls when VoWIFI is disabled.
+     *
+     * @param handle The {@link PhoneAccountHandle}.
+     * @return {@code True} if merging of wifi calls is allowed when VoWIFI is disabled.
+     */
+    boolean isMergeOfWifiCallsAllowedWhenVoWifiOff(final PhoneAccountHandle handle) {
+        synchronized (mAccountsLock) {
+            Optional<AccountEntry> result = mAccounts.stream().filter(
+                    entry -> entry.getPhoneAccountHandle().equals(handle)).findFirst();
+
+            if (result.isPresent()) {
+                return result.get().isMergeOfWifiCallsAllowedWhenVoWifiOff();
+            } else {
+                return false;
+            }
+        }
+    }
+
+    /**
      * @return Reference to the {@code TelecomAccountRegistry}'s subscription manager.
      */
     SubscriptionManager getSubscriptionManager() {
@@ -486,9 +537,11 @@
      * @return The address.
      */
     Uri getAddress(PhoneAccountHandle handle) {
-        for (AccountEntry entry : mAccounts) {
-            if (entry.getPhoneAccountHandle().equals(handle)) {
-                return entry.mAccount.getAddress();
+        synchronized (mAccountsLock) {
+            for (AccountEntry entry : mAccounts) {
+                if (entry.getPhoneAccountHandle().equals(handle)) {
+                    return entry.mAccount.getAddress();
+                }
             }
         }
         return null;
@@ -521,9 +574,11 @@
      * @return {@code True} if an entry exists.
      */
     boolean hasAccountEntryForPhoneAccount(PhoneAccountHandle handle) {
-        for (AccountEntry entry : mAccounts) {
-            if (entry.getPhoneAccountHandle().equals(handle)) {
-                return true;
+        synchronized (mAccountsLock) {
+            for (AccountEntry entry : mAccounts) {
+                if (entry.getPhoneAccountHandle().equals(handle)) {
+                    return true;
+                }
             }
         }
         return false;
@@ -562,28 +617,31 @@
         final boolean phoneAccountsEnabled = mContext.getResources().getBoolean(
                 R.bool.config_pstn_phone_accounts_enabled);
 
-        if (phoneAccountsEnabled) {
-            for (Phone phone : phones) {
-                int subscriptionId = phone.getSubId();
-                Log.d(this, "Phone with subscription id %d", subscriptionId);
-                if (subscriptionId >= 0) {
-                    mAccounts.add(new AccountEntry(phone, false /* emergency */,
-                            false /* isDummy */));
+        synchronized (mAccountsLock) {
+            if (phoneAccountsEnabled) {
+                for (Phone phone : phones) {
+                    int subscriptionId = phone.getSubId();
+                    Log.d(this, "Phone with subscription id %d", subscriptionId);
+                    if (subscriptionId >= 0) {
+                        mAccounts.add(new AccountEntry(phone, false /* emergency */,
+                                false /* isDummy */));
+                    }
                 }
             }
-        }
 
-        // If we did not list ANY accounts, we need to provide a "default" SIM account
-        // for emergency numbers since no actual SIM is needed for dialing emergency
-        // numbers but a phone account is.
-        if (mAccounts.isEmpty()) {
-            mAccounts.add(new AccountEntry(PhoneFactory.getDefaultPhone(), true /* emergency */,
-                    false /* isDummy */));
-        }
+            // If we did not list ANY accounts, we need to provide a "default" SIM account
+            // for emergency numbers since no actual SIM is needed for dialing emergency
+            // numbers but a phone account is.
+            if (mAccounts.isEmpty()) {
+                mAccounts.add(new AccountEntry(PhoneFactory.getDefaultPhone(), true /* emergency */,
+                        false /* isDummy */));
+            }
 
-        // Add a fake account entry.
-        if (DBG && phones.length > 0 && "TRUE".equals(System.getProperty("dummy_sim"))) {
-            mAccounts.add(new AccountEntry(phones[0], false /* emergency */, true /* isDummy */));
+            // Add a fake account entry.
+            if (DBG && phones.length > 0 && "TRUE".equals(System.getProperty("dummy_sim"))) {
+                mAccounts.add(new AccountEntry(phones[0], false /* emergency */,
+                        true /* isDummy */));
+            }
         }
 
         // Clean up any PhoneAccounts that are no longer relevant
@@ -615,9 +673,11 @@
     }
 
     private void tearDownAccounts() {
-        for (AccountEntry entry : mAccounts) {
-            entry.teardown();
+        synchronized (mAccountsLock) {
+            for (AccountEntry entry : mAccounts) {
+                entry.teardown();
+            }
+            mAccounts.clear();
         }
-        mAccounts.clear();
     }
 }
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index e495de5..07cd7b5 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -27,8 +27,10 @@
 import android.telecom.ConferenceParticipant;
 import android.telecom.Connection;
 import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
 import android.telecom.StatusHints;
 import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
 import android.telephony.PhoneNumberUtils;
 import android.util.Pair;
 
@@ -41,6 +43,8 @@
 import com.android.internal.telephony.gsm.SuppServiceNotification;
 
 import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.imsphone.ImsPhone;
+import com.android.phone.PhoneUtils;
 import com.android.phone.R;
 
 import java.lang.Override;
@@ -158,6 +162,10 @@
                 case MSG_SET_VIDEO_STATE:
                     int videoState = (int) msg.obj;
                     setVideoState(videoState);
+
+                    // A change to the video state of the call can influence whether or not it
+                    // can be part of a conference.
+                    refreshConferenceSupported();
                     break;
 
                 case MSG_SET_VIDEO_PROVIDER:
@@ -709,6 +717,10 @@
             if (PhoneNumberUtils.isEmergencyNumber(mOriginalConnection.getAddress())) {
                 mTreatAsEmergencyCall = true;
             }
+
+            // Changing the address of the connection can change whether it is an emergency call or
+            // not, which can impact whether it can be part of a conference.
+            refreshConferenceSupported();
         }
     }
 
@@ -759,6 +771,22 @@
         }
         mIsMultiParty = mOriginalConnection.isMultiparty();
 
+        Bundle extrasToPut = new Bundle();
+        List<String> extrasToRemove = new ArrayList<>();
+        if (mOriginalConnection.isActiveCallDisconnectedOnAnswer()) {
+            extrasToPut.putBoolean(Connection.EXTRA_ANSWERING_DROPS_FG_CALL, true);
+        } else {
+            extrasToRemove.add(Connection.EXTRA_ANSWERING_DROPS_FG_CALL);
+        }
+
+        if (!mOriginalConnection.shouldAllowAddCallDuringVideoCall()) {
+            extrasToPut.putBoolean(Connection.EXTRA_DISABLE_ADD_CALL_DURING_VIDEO_CALL, true);
+        } else {
+            extrasToRemove.add(Connection.EXTRA_DISABLE_ADD_CALL_DURING_VIDEO_CALL);
+        }
+        putExtras(extrasToPut);
+        removeExtras(extrasToRemove);
+
         // updateState can set mOriginalConnection to null if its state is DISCONNECTED, so this
         // should be executed *after* the above setters have run.
         updateState();
@@ -1414,6 +1442,62 @@
     }
 
     /**
+     * Determines whether the connection supports conference calling.  A connection supports
+     * conference calling if it:
+     * 1. Is not an emergency call.
+     * 2. Carrier supports conference calls.
+     * 3. If call is a video call, carrier supports video conference calls.
+     * 4. If call is a wifi call and VoWIFI is disabled and carrier supports merging these calls.
+     */
+    private void refreshConferenceSupported() {
+        boolean isVideoCall = VideoProfile.isVideo(getVideoState());
+        Phone phone = getPhone();
+        boolean isIms = phone.getPhoneType() == PhoneConstants.PHONE_TYPE_IMS;
+        boolean isVoWifiEnabled = false;
+        if (isIms) {
+            ImsPhone imsPhone = (ImsPhone) phone;
+            isVoWifiEnabled = imsPhone.isWifiCallingEnabled();
+        }
+        PhoneAccountHandle phoneAccountHandle = isIms ? PhoneUtils
+                .makePstnPhoneAccountHandle(phone.getDefaultPhone())
+                : PhoneUtils.makePstnPhoneAccountHandle(phone);
+        TelecomAccountRegistry telecomAccountRegistry = TelecomAccountRegistry
+                .getInstance(getPhone().getContext());
+        boolean isConferencingSupported = telecomAccountRegistry
+                .isMergeCallSupported(phoneAccountHandle);
+        boolean isVideoConferencingSupported = 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);
+        boolean isConferenceSupported = true;
+        if (mTreatAsEmergencyCall) {
+            isConferenceSupported = false;
+            Log.d(this, "refreshConferenceSupported = false; emergency call");
+        } else if (!isConferencingSupported) {
+            isConferenceSupported = false;
+            Log.d(this, "refreshConferenceSupported = false; carrier doesn't support conf.");
+        } else if (isVideoCall && !isVideoConferencingSupported) {
+            isConferenceSupported = false;
+            Log.d(this, "refreshConferenceSupported = false; video conf not supported.");
+        } else if (!isMergeOfWifiCallsAllowedWhenVoWifiOff && isWifi() && !isVoWifiEnabled) {
+            isConferenceSupported = false;
+            Log.d(this,
+                    "refreshConferenceSupported = false; can't merge wifi calls when voWifi off.");
+        } else {
+            Log.d(this, "refreshConferenceSupported = true.");
+        }
+
+        if (isConferenceSupported != isConferenceSupported()) {
+            setConferenceSupported(isConferenceSupported);
+            notifyConferenceSupportedChanged(isConferenceSupported);
+        }
+    }
+    /**
      * Provides a mapping from extras keys which may be found in the
      * {@link com.android.internal.telephony.Connection} to their equivalents defined in
      * {@link android.telecom.Connection}.
@@ -1466,6 +1550,8 @@
         } else {
             sb.append("Y");
         }
+        sb.append(" confSupported:");
+        sb.append(mIsConferenceSupported ? "Y" : "N");
         sb.append("]");
         return sb.toString();
     }
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index a4434dd..31d0475 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -175,10 +175,83 @@
             }
         }
 
-        boolean isEmergencyNumber = PhoneNumberUtils.isLocalEmergencyNumber(this, number);
+        final boolean isEmergencyNumber = PhoneNumberUtils.isLocalEmergencyNumber(this, number);
 
-        // Get the right phone object from the account data passed in.
-        final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber);
+        if (isEmergencyNumber && !isRadioOn()) {
+            final Uri emergencyHandle = handle;
+            // By default, Connection based on the default Phone, since we need to return to Telecom
+            // now.
+            final int defaultPhoneType = PhoneFactory.getDefaultPhone().getPhoneType();
+            final Connection emergencyConnection = getTelephonyConnection(request, number,
+                    isEmergencyNumber, emergencyHandle, PhoneFactory.getDefaultPhone());
+            if (mEmergencyCallHelper == null) {
+                mEmergencyCallHelper = new EmergencyCallHelper(this);
+            }
+            mEmergencyCallHelper.enableEmergencyCalling(new EmergencyCallStateListener.Callback() {
+                @Override
+                public void onComplete(EmergencyCallStateListener listener, boolean isRadioReady) {
+                    if (isRadioReady) {
+                        // Get the right phone object since the radio has been turned on
+                        // successfully.
+                        final Phone phone = getPhoneForAccount(request.getAccountHandle(),
+                                isEmergencyNumber);
+                        // If the PhoneType of the Phone being used is different than the Default
+                        // Phone, then we need create a new Connection using that PhoneType and
+                        // replace it in Telecom.
+                        if (phone.getPhoneType() != defaultPhoneType) {
+                            Connection repConnection = getTelephonyConnection(request, number,
+                                    isEmergencyNumber, emergencyHandle, phone);
+                            // If there was a failure, the resulting connection will not be a
+                            // TelephonyConnection, so don't place the call, just return!
+                            if (repConnection instanceof TelephonyConnection) {
+                                placeOutgoingConnection((TelephonyConnection) repConnection, phone,
+                                        request);
+                            }
+                            // Notify Telecom of the new Connection type.
+                            // TODO: Switch out the underlying connection instead of creating a new
+                            // one and causing UI Jank.
+                            addExistingConnection(PhoneUtils.makePstnPhoneAccountHandle(phone),
+                                    repConnection);
+                            // Remove the old connection from Telecom after.
+                            emergencyConnection.setDisconnected(
+                                    DisconnectCauseUtil.toTelecomDisconnectCause(
+                                            android.telephony.DisconnectCause.OUTGOING_CANCELED,
+                                            "Reconnecting outgoing Emergency Call."));
+                            emergencyConnection.destroy();
+                        } else {
+                            placeOutgoingConnection((TelephonyConnection) emergencyConnection,
+                                    phone, request);
+                        }
+                    } else {
+                        Log.w(this, "onCreateOutgoingConnection, failed to turn on radio");
+                        emergencyConnection.setDisconnected(
+                                DisconnectCauseUtil.toTelecomDisconnectCause(
+                                        android.telephony.DisconnectCause.POWER_OFF,
+                                        "Failed to turn on radio."));
+                        emergencyConnection.destroy();
+                    }
+                }
+            });
+            // Return the still unconnected GsmConnection and wait for the Radios to boot before
+            // connecting it to the underlying Phone.
+            return emergencyConnection;
+        } else {
+            // Get the right phone object from the account data passed in.
+            final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber);
+            Connection resultConnection = getTelephonyConnection(request, number, isEmergencyNumber,
+                    handle, phone);
+            // If there was a failure, the resulting connection will not be a TelephonyConnection,
+            // so don't place the call!
+            if(resultConnection instanceof TelephonyConnection) {
+                placeOutgoingConnection((TelephonyConnection) resultConnection, phone, request);
+            }
+            return resultConnection;
+        }
+    }
+
+    private Connection getTelephonyConnection(final ConnectionRequest request, final String number,
+            boolean isEmergencyNumber, final Uri handle, Phone phone) {
+
         if (phone == null) {
             final Context context = getApplicationContext();
             if (context.getResources().getBoolean(R.bool.config_checkSimStateBeforeOutgoingCall)) {
@@ -222,11 +295,12 @@
         // when voice RAT is OOS but Data RAT is present.
         int state = phone.getServiceState().getState();
         if (state == ServiceState.STATE_OUT_OF_SERVICE) {
-            if (phone.getServiceState().getDataNetworkType() == TelephonyManager.NETWORK_TYPE_LTE) {
+            int dataNetType = phone.getServiceState().getDataNetworkType();
+            if (dataNetType == TelephonyManager.NETWORK_TYPE_LTE ||
+                    dataNetType == TelephonyManager.NETWORK_TYPE_LTE_CA) {
                 state = phone.getServiceState().getDataRegState();
             }
         }
-        boolean useEmergencyCallHelper = false;
 
         // If we're dialing a non-emergency number and the phone is in ECM mode, reject the call if
         // carrier configuration specifies that we cannot make non-emergency calls in ECM mode.
@@ -248,11 +322,7 @@
             }
         }
 
-        if (isEmergencyNumber) {
-            if (!phone.isRadioOn()) {
-                useEmergencyCallHelper = true;
-            }
-        } else {
+        if (!isEmergencyNumber) {
             switch (state) {
                 case ServiceState.STATE_IN_SERVICE:
                 case ServiceState.STATE_EMERGENCY_ONLY:
@@ -307,34 +377,6 @@
         connection.setInitializing();
         connection.setVideoState(request.getVideoState());
 
-        if (useEmergencyCallHelper) {
-            if (mEmergencyCallHelper == null) {
-                mEmergencyCallHelper = new EmergencyCallHelper(this);
-            }
-            mEmergencyCallHelper.startTurnOnRadioSequence(phone,
-                    new EmergencyCallHelper.Callback() {
-                        @Override
-                        public void onComplete(boolean isRadioReady) {
-                            if (connection.getState() == Connection.STATE_DISCONNECTED) {
-                                // If the connection has already been disconnected, do nothing.
-                            } else if (isRadioReady) {
-                                connection.setInitialized();
-                                placeOutgoingConnection(connection, phone, request);
-                            } else {
-                                Log.d(this, "onCreateOutgoingConnection, failed to turn on radio");
-                                connection.setDisconnected(
-                                        DisconnectCauseUtil.toTelecomDisconnectCause(
-                                                android.telephony.DisconnectCause.POWER_OFF,
-                                                "Failed to turn on radio."));
-                                connection.destroy();
-                            }
-                        }
-                    });
-
-        } else {
-            placeOutgoingConnection(connection, phone, request);
-        }
-
         return connection;
     }
 
@@ -512,6 +554,14 @@
 
     }
 
+    private boolean isRadioOn() {
+        boolean result = false;
+        for (Phone phone : PhoneFactory.getPhones()) {
+            result |= phone.isRadioOn();
+        }
+        return result;
+    }
+
     private void placeOutgoingConnection(
             TelephonyConnection connection, Phone phone, ConnectionRequest request) {
         String number = connection.getAddress().getSchemeSpecificPart();
@@ -573,15 +623,6 @@
             returnConnection.setVideoPauseSupported(
                     TelecomAccountRegistry.getInstance(this).isVideoPauseSupported(
                             phoneAccountHandle));
-            boolean isEmergencyCall = (address != null && PhoneNumberUtils.isEmergencyNumber(
-                    address.getSchemeSpecificPart()));
-            boolean isVideoCall = VideoProfile.isVideo(videoState);
-            boolean isConferencingSupported = TelecomAccountRegistry.getInstance(this)
-                    .isMergeCallSupported(phoneAccountHandle);
-            boolean isVideoConferencingSupported = TelecomAccountRegistry.getInstance(this)
-                    .isVideoConferencingSupported(phoneAccountHandle);
-            returnConnection.setConferenceSupported(!isEmergencyCall && isConferencingSupported
-                    && (!isVideoCall || (isVideoCall && isVideoConferencingSupported)));
         }
         return returnConnection;
     }
diff --git a/tests/Android.mk b/tests/Android.mk
index 6cc0355..e1b564f 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -25,6 +25,8 @@
 
 LOCAL_MODULE_TAGS := tests
 
+LOCAL_JAVA_LIBRARIES := telephony-common
+
 LOCAL_INSTRUMENTATION_FOR := TeleService
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 8900568..cae4c1b 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -62,17 +62,16 @@
     </application>
 
     <!--
-        The prefered way is to use 'runtest':
-           runtest phone-unit
+        To run all tests:
+            adb shell am instrument -w
+                com.android.phone.tests/android.support.test.runner.AndroidJUnitRunner
 
-         runtest is a wrapper around 'adb shell'. The low level shell command is:
-           adb shell am instrument -w com.android.phone.tests/android.test.InstrumentationTestRunner
+        To run a single class test:
+            adb shell am instrument -e class com.android.phone.unit.FooUnitTest
+                -w com.android.phone.tests/android.support.test.runner.AndroidJUnitRunner
 
-         To run a single test case:
-           adb shell am instrument -w com.android.phone.tests/android.test.InstrumentationTestRunner
-                                   -e com.android.phone.unit.FooUnitTest
     -->
-    <instrumentation android:name="android.test.InstrumentationTestRunner"
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
         android:targetPackage="com.android.phone"
         android:label="Phone application tests." />
 </manifest>
diff --git a/tests/src/com/android/TelephonyTestBase.java b/tests/src/com/android/TelephonyTestBase.java
new file mode 100644
index 0000000..6dee12b
--- /dev/null
+++ b/tests/src/com/android/TelephonyTestBase.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android;
+
+import android.content.Context;
+import android.os.Handler;
+import android.support.test.InstrumentationRegistry;
+
+import com.android.phone.MockitoHelper;
+
+import org.mockito.MockitoAnnotations;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Helper class to load Mockito Resources into a test.
+ */
+public class TelephonyTestBase {
+
+    protected Context mContext;
+    MockitoHelper mMockitoHelper = new MockitoHelper();
+
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getTargetContext();
+        mMockitoHelper.setUp(mContext, getClass());
+        MockitoAnnotations.initMocks(this);
+    }
+
+    public void tearDown() throws Exception {
+        mMockitoHelper.tearDown();
+    }
+
+    protected final void waitForHandlerAction(Handler h, long timeoutMillis) {
+        final CountDownLatch lock = new CountDownLatch(1);
+        h.post(lock::countDown);
+        while (lock.getCount() > 0) {
+            try {
+                lock.await(timeoutMillis, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                // do nothing
+            }
+        }
+    }
+
+    protected final void waitForHandlerActionDelayed(Handler h, long timeoutMillis, long delayMs) {
+        final CountDownLatch lock = new CountDownLatch(1);
+        h.postDelayed(lock::countDown, delayMs);
+        while (lock.getCount() > 0) {
+            try {
+                lock.await(timeoutMillis, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                // do nothing
+            }
+        }
+    }
+}
diff --git a/tests/src/com/android/phone/MockitoHelper.java b/tests/src/com/android/phone/MockitoHelper.java
index 3da5d6e..7998030 100644
--- a/tests/src/com/android/phone/MockitoHelper.java
+++ b/tests/src/com/android/phone/MockitoHelper.java
@@ -16,6 +16,8 @@
 
 package com.android.phone;
 
+import android.content.Context;
+
 import com.android.services.telephony.Log;
 
 /**
@@ -24,6 +26,7 @@
 public final class MockitoHelper {
 
     private static final String TAG = "MockitoHelper";
+    private static final String DEXCACHE = "dexmaker.dexcache";
 
     private ClassLoader mOriginalClassLoader;
     private Thread mContextThread;
@@ -34,7 +37,7 @@
      *
      * @param packageClass test case class
      */
-    public void setUp(Class<?> packageClass) throws Exception {
+    public void setUp(Context context, Class<?> packageClass) throws Exception {
         // makes a copy of the context classloader
         mContextThread = Thread.currentThread();
         mOriginalClassLoader = mContextThread.getContextClassLoader();
@@ -42,6 +45,9 @@
         Log.v(TAG, "Changing context classloader from " + mOriginalClassLoader
                 + " to " + newClassLoader);
         mContextThread.setContextClassLoader(newClassLoader);
+        String dexCache = context.getCacheDir().toString();
+        Log.v(this, "Setting property %s to %s", DEXCACHE, dexCache);
+        System.setProperty(DEXCACHE, dexCache);
     }
 
     /**
@@ -50,5 +56,6 @@
     public void tearDown() throws Exception {
         Log.v(TAG, "Restoring context classloader to " + mOriginalClassLoader);
         mContextThread.setContextClassLoader(mOriginalClassLoader);
+        System.clearProperty(DEXCACHE);
     }
 }
\ No newline at end of file
diff --git a/tests/src/com/android/phone/common/mail/MailTransportTest.java b/tests/src/com/android/phone/common/mail/MailTransportTest.java
index 6acd517..9eaef6b 100644
--- a/tests/src/com/android/phone/common/mail/MailTransportTest.java
+++ b/tests/src/com/android/phone/common/mail/MailTransportTest.java
@@ -61,7 +61,7 @@
     @Override
     public void setUp() throws Exception {
         super.setUp();
-        mMokitoHelper.setUp(getClass());
+        mMokitoHelper.setUp(getContext(), getClass());
         MockitoAnnotations.initMocks(this);
     }
 
diff --git a/tests/src/com/android/phone/vvm/omtp/OmtpBootCompletedReceiverTests.java b/tests/src/com/android/phone/vvm/omtp/OmtpBootCompletedReceiverTests.java
deleted file mode 100644
index 1924d9f..0000000
--- a/tests/src/com/android/phone/vvm/omtp/OmtpBootCompletedReceiverTests.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.phone.vvm.omtp;
-
-import android.content.Context;
-import android.content.Intent;
-import android.preference.PreferenceManager;
-import android.test.AndroidTestCase;
-import android.util.ArraySet;
-
-import com.android.phone.vvm.omtp.OmtpBootCompletedReceiver.SubIdProcessor;
-
-import java.util.Set;
-
-public class OmtpBootCompletedReceiverTests extends AndroidTestCase {
-    OmtpBootCompletedReceiver mReceiver = new OmtpBootCompletedReceiver();
-    @Override
-    public void setUp() {
-    }
-
-    @Override
-    public void tearDown() {
-        PreferenceManager
-                .getDefaultSharedPreferences(getContext().createDeviceProtectedStorageContext())
-                .edit().clear().apply();
-    }
-
-    public void testReadWriteList() {
-        readWriteList(new int[] {1});
-    }
-
-    public void testReadWriteList_Multiple() {
-        readWriteList(new int[] {1, 2});
-    }
-
-    public void testReadWriteList_Duplicate() {
-        readWriteList(new int[] {1, 1});
-    }
-
-    private void readWriteList(int[] values) {
-        for (int value : values) {
-            OmtpBootCompletedReceiver.addDeferredSubId(getContext(), value);
-        }
-        TestSubIdProcessor processor = new TestSubIdProcessor(values);
-        mReceiver.setSubIdProcessorForTest(processor);
-        Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED);
-        mReceiver.onReceive(getContext(), intent);
-        processor.assertMatch();
-        // after onReceive() is called the list should be empty
-        TestSubIdProcessor emptyProcessor = new TestSubIdProcessor(new int[] {});
-        mReceiver.setSubIdProcessorForTest(processor);
-        mReceiver.onReceive(getContext(), intent);
-        processor.assertMatch();
-    }
-
-    private static class TestSubIdProcessor implements SubIdProcessor {
-        private final Set<Integer> mExpectedSubIds;
-
-        public TestSubIdProcessor(int[] expectedSubIds) {
-            mExpectedSubIds = new ArraySet<>();
-            for(int subId : expectedSubIds){
-                mExpectedSubIds.add(subId);
-            }
-        }
-
-        @Override
-        public void process(Context context, int subId){
-            assertTrue(mExpectedSubIds.contains(subId));
-            mExpectedSubIds.remove(subId);
-        }
-
-        public void assertMatch(){
-            assertTrue(mExpectedSubIds.isEmpty());
-        }
-    }
-}
diff --git a/tests/src/com/android/phone/vvm/omtp/StatusMessageTest.java b/tests/src/com/android/phone/vvm/omtp/StatusMessageTest.java
new file mode 100644
index 0000000..fd3aa2c
--- /dev/null
+++ b/tests/src/com/android/phone/vvm/omtp/StatusMessageTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.phone.vvm.omtp;
+
+import android.os.Bundle;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.phone.vvm.omtp.sms.StatusMessage;
+
+import junit.framework.TestCase;
+
+@VisibleForTesting
+public class StatusMessageTest extends TestCase {
+
+    public void testStatusMessage() {
+        Bundle bundle = new Bundle();
+        bundle.putString(OmtpConstants.PROVISIONING_STATUS, "status");
+        bundle.putString(OmtpConstants.RETURN_CODE, "code");
+        bundle.putString(OmtpConstants.SUBSCRIPTION_URL, "url");
+        bundle.putString(OmtpConstants.SERVER_ADDRESS, "address");
+        bundle.putString(OmtpConstants.TUI_ACCESS_NUMBER, "tui");
+        bundle.putString(OmtpConstants.CLIENT_SMS_DESTINATION_NUMBER, "sms");
+        bundle.putString(OmtpConstants.IMAP_PORT, "1234");
+        bundle.putString(OmtpConstants.IMAP_USER_NAME, "username");
+        bundle.putString(OmtpConstants.IMAP_PASSWORD, "password");
+        bundle.putString(OmtpConstants.SMTP_PORT, "s1234");
+        bundle.putString(OmtpConstants.SMTP_USER_NAME, "susername");
+        bundle.putString(OmtpConstants.SMTP_PASSWORD, "spassword");
+
+        StatusMessage message = new StatusMessage(bundle);
+        assertEquals("status", message.getProvisioningStatus());
+        assertEquals("code", message.getReturnCode());
+        assertEquals("url", message.getSubscriptionUrl());
+        assertEquals("address", message.getServerAddress());
+        assertEquals("tui", message.getTuiAccessNumber());
+        assertEquals("sms", message.getClientSmsDestinationNumber());
+        assertEquals("1234", message.getImapPort());
+        assertEquals("username", message.getImapUserName());
+        assertEquals("password", message.getImapPassword());
+        assertEquals("s1234", message.getSmtpPort());
+        assertEquals("susername", message.getSmtpUserName());
+        assertEquals("spassword", message.getSmtpPassword());
+    }
+
+    public void testSyncMessage_EmptyBundle() {
+        StatusMessage message = new StatusMessage(new Bundle());
+        assertEquals("", message.getProvisioningStatus());
+        assertEquals("", message.getReturnCode());
+        assertEquals("", message.getSubscriptionUrl());
+        assertEquals("", message.getServerAddress());
+        assertEquals("", message.getTuiAccessNumber());
+        assertEquals("", message.getClientSmsDestinationNumber());
+        assertEquals("", message.getImapPort());
+        assertEquals("", message.getImapUserName());
+        assertEquals("", message.getImapPassword());
+        assertEquals("", message.getSmtpPort());
+        assertEquals("", message.getSmtpUserName());
+        assertEquals("", message.getSmtpPassword());
+    }
+}
diff --git a/tests/src/com/android/phone/vvm/omtp/SyncMessageTest.java b/tests/src/com/android/phone/vvm/omtp/SyncMessageTest.java
new file mode 100644
index 0000000..61ae400
--- /dev/null
+++ b/tests/src/com/android/phone/vvm/omtp/SyncMessageTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.phone.vvm.omtp;
+
+import android.os.Bundle;
+
+import com.android.phone.vvm.omtp.sms.SyncMessage;
+
+import junit.framework.TestCase;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Locale;
+
+public class SyncMessageTest extends TestCase {
+
+    public void testSyncMessage() {
+        Bundle bundle = new Bundle();
+        bundle.putString(OmtpConstants.SYNC_TRIGGER_EVENT, "event");
+        bundle.putString(OmtpConstants.MESSAGE_UID, "uid");
+        bundle.putString(OmtpConstants.MESSAGE_LENGTH, "1");
+        bundle.putString(OmtpConstants.CONTENT_TYPE, "type");
+        bundle.putString(OmtpConstants.SENDER, "sender");
+        bundle.putString(OmtpConstants.NUM_MESSAGE_COUNT, "2");
+        bundle.putString(OmtpConstants.TIME, "29/08/1997 02:14 -0400");
+
+        SyncMessage message = new SyncMessage(bundle);
+        assertEquals("event", message.getSyncTriggerEvent());
+        assertEquals("uid", message.getId());
+        assertEquals(1, message.getLength());
+        assertEquals("type", message.getContentType());
+        assertEquals("sender", message.getSender());
+        assertEquals(2, message.getNewMessageCount());
+        try {
+            assertEquals(new SimpleDateFormat(
+                    OmtpConstants.DATE_TIME_FORMAT, Locale.US)
+                    .parse("29/08/1997 02:14 -0400").getTime(), message.getTimestampMillis());
+        } catch (ParseException e) {
+            throw new AssertionError(e.toString());
+        }
+    }
+
+    public void testSyncMessage_EmptyBundle() {
+        SyncMessage message = new SyncMessage(new Bundle());
+        assertEquals("", message.getSyncTriggerEvent());
+        assertEquals("", message.getId());
+        assertEquals(0, message.getLength());
+        assertEquals("", message.getContentType());
+        assertEquals("", message.getSender());
+        assertEquals(0, message.getNewMessageCount());
+        assertEquals(0, message.getTimestampMillis());
+    }
+}
diff --git a/tests/src/com/android/services/telephony/EmergencyCallStateListenerTest.java b/tests/src/com/android/services/telephony/EmergencyCallStateListenerTest.java
new file mode 100644
index 0000000..64cf052
--- /dev/null
+++ b/tests/src/com/android/services/telephony/EmergencyCallStateListenerTest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.services.telephony;
+
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.telephony.ServiceState;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.TelephonyTestBase;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.isNull;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.when;
+
+/**
+ * Tests the EmergencyCallStateListener, which listens to one Phone and waits until its service
+ * state changes to accepting emergency calls or in service. If it can not find a tower to camp onto
+ * for emergency calls, then it will fail after a timeout period.
+ */
+@RunWith(AndroidJUnit4.class)
+public class EmergencyCallStateListenerTest extends TelephonyTestBase {
+
+    private static final long TIMEOUT_MS = 100;
+
+    @Mock Phone mMockPhone;
+    @Mock EmergencyCallStateListener.Callback mCallback;
+    EmergencyCallStateListener mListener;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        mListener = new EmergencyCallStateListener();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mListener.getHandler().removeCallbacksAndMessages(null);
+        super.tearDown();
+    }
+
+    @Test
+    public void testRegisterForCallback() {
+        mListener.waitForRadioOn(mMockPhone, mCallback);
+
+        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
+
+        verify(mMockPhone).unregisterForServiceStateChanged(any(Handler.class));
+        verify(mMockPhone).registerForServiceStateChanged(any(Handler.class),
+                eq(EmergencyCallStateListener.MSG_SERVICE_STATE_CHANGED), isNull());
+    }
+
+    @Test
+    public void testPhoneChangeState_InService() {
+        ServiceState state = new ServiceState();
+        state.setState(ServiceState.STATE_IN_SERVICE);
+        when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE);
+        mListener.waitForRadioOn(mMockPhone, mCallback);
+        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
+
+        mListener.getHandler().obtainMessage(EmergencyCallStateListener.MSG_SERVICE_STATE_CHANGED,
+                new AsyncResult(null, state, null)).sendToTarget();
+
+        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
+        verify(mCallback).onComplete(eq(mListener), eq(true));
+    }
+
+    @Test
+    public void testPhoneChangeState_EmergencyCalls() {
+        ServiceState state = new ServiceState();
+        state.setState(ServiceState.STATE_OUT_OF_SERVICE);
+        state.setEmergencyOnly(true);
+        when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE);
+        when(mMockPhone.getServiceState()).thenReturn(state);
+        mListener.waitForRadioOn(mMockPhone, mCallback);
+        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
+
+        mListener.getHandler().obtainMessage(EmergencyCallStateListener.MSG_SERVICE_STATE_CHANGED,
+                new AsyncResult(null, state, null)).sendToTarget();
+
+        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
+        verify(mCallback).onComplete(eq(mListener), eq(true));
+    }
+
+    @Test
+    public void testPhoneChangeState_OutOfService() {
+        ServiceState state = new ServiceState();
+        state.setState(ServiceState.STATE_OUT_OF_SERVICE);
+        when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE);
+        when(mMockPhone.getServiceState()).thenReturn(state);
+        mListener.waitForRadioOn(mMockPhone, mCallback);
+        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
+
+        // Don't expect any answer, since it is not the one that we want and the timeout for giving
+        // up hasn't expired yet.
+        mListener.getHandler().obtainMessage(EmergencyCallStateListener.MSG_SERVICE_STATE_CHANGED,
+                new AsyncResult(null, state, null)).sendToTarget();
+
+        waitForHandlerAction(mListener.getHandler(), TIMEOUT_MS);
+        verify(mCallback, never()).onComplete(any(EmergencyCallStateListener.class), anyBoolean());
+    }
+
+    @Test
+    public void testTimeout_EmergencyCalls() {
+        ServiceState state = new ServiceState();
+        state.setState(ServiceState.STATE_OUT_OF_SERVICE);
+        state.setEmergencyOnly(true);
+        when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE);
+        when(mMockPhone.getServiceState()).thenReturn(state);
+        mListener.waitForRadioOn(mMockPhone, mCallback);
+        mListener.setTimeBetweenRetriesMillis(500);
+
+        // Wait for the timer to expire and check state manually in onRetryTimeout
+        waitForHandlerActionDelayed(mListener.getHandler(), TIMEOUT_MS, 600);
+
+        verify(mCallback).onComplete(eq(mListener), eq(true));
+    }
+
+    @Test
+    public void testTimeout_RetryFailure() {
+        ServiceState state = new ServiceState();
+        state.setState(ServiceState.STATE_POWER_OFF);
+        when(mMockPhone.getState()).thenReturn(PhoneConstants.State.IDLE);
+        when(mMockPhone.getServiceState()).thenReturn(state);
+        mListener.waitForRadioOn(mMockPhone, mCallback);
+        mListener.setTimeBetweenRetriesMillis(100);
+        mListener.setMaxNumRetries(2);
+
+        // Wait for the timer to expire and check state manually in onRetryTimeout
+        waitForHandlerActionDelayed(mListener.getHandler(), TIMEOUT_MS, 600);
+
+        verify(mCallback).onComplete(eq(mListener), eq(false));
+        verify(mMockPhone, times(2)).setRadioPower(eq(true));
+    }
+
+}