Merge commit '21c5e2d' into mergemeister

Change-Id: I4203215f522fd2efda575aa56d7f7d75f856add8
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index dd6593b..e3a6dcd 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -533,6 +533,12 @@
                 android:uiOptions="splitActionBarWhenNarrow">
         </activity>
 
+        <activity android:name="ImsEditor"
+                android:theme="@style/DialerSettingsLight"
+                android:configChanges="orientation|screenSize|keyboardHidden"
+                android:uiOptions="splitActionBarWhenNarrow">
+        </activity>
+
         <!-- End SIP -->
 
         <activity android:name="ErrorDialogActivity"
diff --git a/res/values/array.xml b/res/values/array.xml
new file mode 100644
index 0000000..79b6034
--- /dev/null
+++ b/res/values/array.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<!-- Array resources for the Phone app. -->
+<resources>
+    <!-- Array resource for IMS VT call quality entries-->
+    <string-array translatable="true" name="ims_vt_call_quality_entries">
+        <item>@string/ims_vt_call_quality_low</item>
+        <item>@string/ims_vt_call_quality_high</item>
+    </string-array>
+
+    <!-- Array resource for IMS VT call quality values-->
+    <string-array translatable="false" name="ims_vt_call_quality_values">
+        <item>0</item>
+        <item>1</item>
+    </string-array>
+</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index e49310d..408d570 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -758,7 +758,8 @@
     <string name="tty_mode_option_summary">Set TTY mode</string>
     <string name="auto_retry_mode_title">Auto-retry</string>
     <string name="auto_retry_mode_summary">Enable Auto-retry mode</string>
-
+    <!-- TTY Mode change is NOT allowed during a video call -->
+    <string name="tty_mode_not_allowed_video_call">TTY Mode change is not allowed during a video call</string>
     <!-- FDN list screen: menu item label -->
     <string name="menu_add">Add contact</string>
     <!-- FDN list screen: menu item label -->
@@ -1300,4 +1301,18 @@
     <!-- String used in place of a message that could not be properly decoded (e.g. bad base64
        data was received.) [CHAR LIMIT=none] -->
     <string name="message_decode_error">There was an error while decoding the message.</string>
+
+    <!-- IMS settings related strings -->
+    <!-- Title of the ims editor screen. [CHAR LIMIT=NONE] -->
+    <string name="ims_edit_title">IMS account details</string>
+    <!-- Title displayed IMS account settings in the sip settings category. [CHAR LIMIT=NONE] -->
+    <string name="ims_accounts_title">IMS Account</string>
+
+    <!-- VT call quality settings. -->
+    <string translatable="false" name="ims_vt_call_quality">VtCallQuality</string>
+    <string name="ims_vt_call_quality_title">VT Call Quality</string>
+    <string name="ims_vt_call_quality_low">Low</string>
+    <string name="ims_vt_call_quality_high">High</string>
+    <string name="ims_vt_call_quality_unknown">Unknown</string>
+    <string name="ims_vt_call_quality_set_failed">Failed to set video quality</string>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 3c192cc..9d2d47f 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -262,6 +262,7 @@
         <item name="android:windowBackground">@color/emergency_dialer_background</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>
     </style>
 
     <style name="SimImportTheme" parent="@android:style/Theme.Material.Light">
diff --git a/res/xml/call_feature_setting.xml b/res/xml/call_feature_setting.xml
index f696e2c..52a8815 100644
--- a/res/xml/call_feature_setting.xml
+++ b/res/xml/call_feature_setting.xml
@@ -82,4 +82,14 @@
 
     </PreferenceScreen>
 
+    <PreferenceScreen
+        android:key="ims_account_settings_key"
+        android:title="@string/ims_accounts_title"
+        android:persistent="false">
+
+        <intent android:action="android.intent.action.MAIN"
+            android:targetPackage="com.android.phone"
+            android:targetClass="com.android.phone.ImsEditor" />
+    </PreferenceScreen>
+
 </PreferenceScreen>
diff --git a/res/xml/ims_edit.xml b/res/xml/ims_edit.xml
new file mode 100644
index 0000000..f16e2f5
--- /dev/null
+++ b/res/xml/ims_edit.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
+        android:title="@string/ims_edit_title"
+        android:persistent="false">
+
+    <ListPreference
+        android:key="@string/ims_vt_call_quality"
+        android:title="@string/ims_vt_call_quality_title"
+        android:entries="@array/ims_vt_call_quality_entries"
+        android:entryValues="@array/ims_vt_call_quality_values"
+        android:summary="@string/ims_vt_call_quality_unknown"
+        android:persistent="false"
+        android:enabled="true"
+        android:dialogTitle="@string/ims_vt_call_quality_title"/>
+
+</PreferenceScreen>
diff --git a/src/com/android/phone/ims/ImsEditor.java b/src/com/android/phone/ims/ImsEditor.java
new file mode 100644
index 0000000..bbd4526
--- /dev/null
+++ b/src/com/android/phone/ims/ImsEditor.java
@@ -0,0 +1,170 @@
+/* Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.os.Bundle;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.telephony.SubscriptionManager;
+import android.util.Log;
+import android.widget.Toast;
+
+import com.android.ims.ImsConfig;
+import com.android.ims.ImsConfigListener;
+import com.android.ims.ImsException;
+import com.android.ims.ImsManager;
+import com.android.phone.R;
+
+/**
+ * The activity class for editing a new or existing IMS profile.
+ */
+public class ImsEditor extends PreferenceActivity
+        implements Preference.OnPreferenceChangeListener {
+
+    private static final String TAG = ImsEditor.class.getSimpleName();
+
+    private ListPreference mVideoCallQuality;
+    private ImsConfig mImsConfig;
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        Log.d(TAG, "onResume");
+        getVideoQuality();
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.v(TAG, "start profile editor");
+        super.onCreate(savedInstanceState);
+
+        addPreferencesFromResource(R.xml.ims_edit);
+        PreferenceScreen screen = getPreferenceScreen();
+
+        mVideoCallQuality = (ListPreference) screen
+                .findPreference(getString(R.string.ims_vt_call_quality));
+        mVideoCallQuality.setOnPreferenceChangeListener(this);
+
+        try {
+            ImsManager imsManager = ImsManager.getInstance(getBaseContext(),
+                    SubscriptionManager.getDefaultVoiceSubId());
+            mImsConfig = imsManager.getConfigInterface();
+        } catch (ImsException e) {
+            mImsConfig = null;
+            Log.e(TAG, "ImsService is not running");
+        }
+    }
+
+    private ImsConfigListener imsConfigListener = new ImsConfigListener.Stub() {
+        public void onGetVideoQuality(int status, int quality) {
+            if (hasRequestFailed(status)) {
+                quality = ImsConfig.OperationValuesConstants.VIDEO_QUALITY_UNKNOWN;
+                Log.e(TAG, "onGetVideoQuality: failed. errorCode = " + status);
+            }
+            Log.d(TAG, "onGetVideoQuality: value = " + quality);
+            loadVideoCallQualityPrefs(quality);
+        }
+
+        public void onSetVideoQuality(int status) {
+            if (hasRequestFailed(status)) {
+                Log.e(TAG, "onSetVideoQuality: set failed. errorCode = " + status);
+                Toast.makeText(getApplicationContext(), R.string.ims_vt_call_quality_set_failed,
+                        Toast.LENGTH_SHORT).show();
+                getVideoQuality(); // Set request failed, get current value.
+            } else {
+                Log.d(TAG, "onSetVideoQuality: set succeeded.");
+            }
+        }
+
+        public void onGetFeatureResponse(int feature, int network, int value, int status) {
+            //TODO not required as of now
+        }
+
+        public void onSetFeatureResponse(int feature, int network, int value, int status) {
+            //TODO not required as of now
+        }
+    };
+
+    @Override
+    public boolean onPreferenceChange(Preference pref, Object newValue) {
+        if (pref.equals(mVideoCallQuality)) {
+            if (newValue == null) {
+                Log.e(TAG, "onPreferenceChange invalid value received");
+            } else {
+                final int quality = Integer.parseInt(newValue.toString());
+                boolean result = setVideoQuality(quality);
+                if (result) {
+                    loadVideoCallQualityPrefs(quality);
+                }
+                return result;
+            }
+        }
+        return true;
+    }
+
+    private void loadVideoCallQualityPrefs(int vqValue) {
+        Log.d(TAG, "loadVideoCallQualityPrefs, vqValue = " + vqValue);
+        final String videoQuality = videoQualityToString(vqValue);
+        mVideoCallQuality.setValue(String.valueOf(vqValue));
+        mVideoCallQuality.setSummary(videoQuality);
+    }
+
+    private void getVideoQuality() {
+        try {
+            if (mImsConfig != null) {
+                mImsConfig.getVideoQuality(imsConfigListener);
+            } else {
+                loadVideoCallQualityPrefs(ImsConfig.OperationValuesConstants.VIDEO_QUALITY_UNKNOWN);
+                Log.e(TAG, "getVideoQuality failed. mImsConfig is null");
+            }
+        } catch (ImsException e) {
+            Log.e(TAG, "getVideoQuality failed. Exception = " + e);
+        }
+    }
+
+    private boolean setVideoQuality(int quality) {
+        try {
+            if (mImsConfig != null) {
+                mImsConfig.setVideoQuality(quality, imsConfigListener);
+            } else {
+                Log.e(TAG, "setVideoQuality failed. mImsConfig is null");
+                return false;
+            }
+        } catch (ImsException e) {
+            Log.e(TAG, "setVideoQuality failed. Exception = " + e);
+            return false;
+        }
+        return true;
+    }
+
+    private boolean hasRequestFailed(int result) {
+        return (result != ImsConfig.OperationStatusConstants.SUCCESS);
+    }
+
+    private String videoQualityToString(int quality) {
+        switch (quality) {
+            case ImsConfig.OperationValuesConstants.VIDEO_QUALITY_HIGH:
+                return getString(R.string.ims_vt_call_quality_high);
+            case ImsConfig.OperationValuesConstants.VIDEO_QUALITY_LOW:
+                return getString(R.string.ims_vt_call_quality_low);
+            case ImsConfig.OperationValuesConstants.VIDEO_QUALITY_UNKNOWN:
+            default:
+                return getString(R.string.ims_vt_call_quality_unknown);
+        }
+    }
+}
diff --git a/src/com/android/phone/settings/VoicemailDialogUtil.java b/src/com/android/phone/settings/VoicemailDialogUtil.java
index 26bc227..32cb37a 100644
--- a/src/com/android/phone/settings/VoicemailDialogUtil.java
+++ b/src/com/android/phone/settings/VoicemailDialogUtil.java
@@ -34,11 +34,12 @@
     public static final int VM_FWD_SAVING_DIALOG = 601;
     public static final int VM_FWD_READING_DIALOG = 602;
     public static final int VM_REVERTING_DIALOG = 603;
+    public static final int TTY_SET_RESPONSE_ERROR = 800;
 
     public static Dialog getDialog(VoicemailSettingsActivity parent, int id) {
         if ((id == VM_RESPONSE_ERROR_DIALOG) || (id == VM_NOCHANGE_ERROR_DIALOG) ||
             (id == FWD_SET_RESPONSE_ERROR_DIALOG) || (id == FWD_GET_RESPONSE_ERROR_DIALOG) ||
-                (id == VM_CONFIRM_DIALOG)) {
+                (id == VM_CONFIRM_DIALOG) || (id == TTY_SET_RESPONSE_ERROR)) {
 
             AlertDialog.Builder b = new AlertDialog.Builder(parent);
 
@@ -74,6 +75,12 @@
                     b.setPositiveButton(R.string.alert_dialog_yes, parent);
                     b.setNegativeButton(R.string.alert_dialog_no, parent);
                     break;
+                case TTY_SET_RESPONSE_ERROR:
+                    titleId = R.string.tty_mode_option_title;
+                    msgId = R.string.tty_mode_not_allowed_video_call;
+                    b.setIconAttribute(android.R.attr.alertDialogIcon);
+                    b.setPositiveButton(R.string.ok, parent);
+                    break;
                 default:
                     msgId = R.string.exception_error;
                     // Set Button 3, tells the activity that the error is
diff --git a/src/com/android/services/telephony/CdmaConnection.java b/src/com/android/services/telephony/CdmaConnection.java
index 74e0673..d2250d4 100644
--- a/src/com/android/services/telephony/CdmaConnection.java
+++ b/src/com/android/services/telephony/CdmaConnection.java
@@ -155,13 +155,16 @@
         mIsCallWaiting = originalConnection != null &&
                 originalConnection.getState() == Call.State.WAITING;
 
-        if (state == android.telecom.Connection.STATE_DIALING) {
-            if (isEmergency()) {
-                mEmergencyTonePlayer.start();
+        if (mEmergencyTonePlayer != null) {
+            if (state == android.telecom.Connection.STATE_DIALING) {
+                if (isEmergency()) {
+                    mEmergencyTonePlayer.start();
+                }
+            } else {
+                // No need to check if it is an emergency call, since it is a no-op if it
+                // isn't started.
+                mEmergencyTonePlayer.stop();
             }
-        } else {
-            // No need to check if it is an emergency call, since it is a no-op if it isn't started.
-            mEmergencyTonePlayer.stop();
         }
 
         super.onStateChanged(state);
diff --git a/src/com/android/services/telephony/ImsConference.java b/src/com/android/services/telephony/ImsConference.java
index 46b3eef..ced36a1 100644
--- a/src/com/android/services/telephony/ImsConference.java
+++ b/src/com/android/services/telephony/ImsConference.java
@@ -21,7 +21,11 @@
 import android.telecom.ConferenceParticipant;
 import android.telecom.Connection;
 import android.telecom.DisconnectCause;
+import android.telecom.Log;
 import android.telecom.PhoneAccountHandle;
+import android.telecom.VideoProfile;
+import android.telecom.Conference.Listener;
+import android.telecom.Connection.VideoProvider;
 
 import com.android.internal.telephony.Call;
 import com.android.internal.telephony.CallStateException;
@@ -145,6 +149,28 @@
             TelephonyConnection telephonyConnection = (TelephonyConnection) c;
             handleConferenceParticipantsUpdate(telephonyConnection, participants);
         }
+
+        @Override
+        public void onVideoStateChanged(android.telecom.Connection c, int videoState) {
+            Log.d(this, "onVideoStateChanged video state %d", videoState);
+            setVideoState(c, videoState);
+        }
+
+        @Override
+        public void onVideoProviderChanged(android.telecom.Connection c,
+                Connection.VideoProvider videoProvider) {
+            Log.d(this, "onVideoProviderChanged: Connection: %s, VideoProvider: %s", c,
+                    videoProvider);
+            setVideoProvider(c, videoProvider);
+        }
+
+        @Override
+        public void onConnectionCapabilitiesChanged(Connection c, int connectionCapabilities) {
+            Log.d(this, "onCallCapabilitiesChanged: Connection: %s, callCapabilities: %s", c,
+                    connectionCapabilities);
+            int capabilites = ImsConference.this.getCapabilities();
+            setCapabilities(applyVideoCapabilities(capabilites, connectionCapabilities));
+        }
     };
 
     /**
@@ -192,6 +218,47 @@
                 Connection.CAPABILITY_HOLD |
                 Connection.CAPABILITY_MUTE
         );
+
+        if (conferenceHost != null && conferenceHost.getCall() != null
+                && conferenceHost.getCall().getPhone() != null) {
+            mPhoneAccount = PhoneUtils.makePstnPhoneAccountHandle(
+                    conferenceHost.getCall().getPhone());
+            Log.v(this, "set phacc to " + mPhoneAccount);
+        }
+
+        int capabilities = Connection.CAPABILITY_SUPPORT_HOLD | Connection.CAPABILITY_HOLD |
+                Connection.CAPABILITY_MUTE;
+
+        capabilities = applyVideoCapabilities(capabilities, mConferenceHost.getCallCapabilities());
+        setConnectionCapabilities(capabilities);
+
+    }
+
+    private int applyVideoCapabilities(int conferenceCapabilities, int capabilities) {
+        if (can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL)) {
+            conferenceCapabilities = applyCapability(conferenceCapabilities,
+                    Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
+        } else {
+            conferenceCapabilities = removeCapability(conferenceCapabilities,
+                    Connection.CAPABILITY_SUPPORTS_VT_LOCAL_BIDIRECTIONAL);
+        }
+
+        if (can(capabilities, Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL)) {
+            conferenceCapabilities = applyCapability(conferenceCapabilities,
+                    Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
+        } else {
+            conferenceCapabilities = removeCapability(conferenceCapabilities,
+                    Connection.CAPABILITY_SUPPORTS_VT_REMOTE_BIDIRECTIONAL);
+        }
+
+        if (can(capabilities, Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO)) {
+            conferenceCapabilities = applyCapability(conferenceCapabilities,
+                    Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO);
+        } else {
+            conferenceCapabilities = removeCapability(conferenceCapabilities,
+                    Connection.CAPABILITY_CAN_UPGRADE_TO_VIDEO);
+        }
+        return conferenceCapabilities;
     }
 
     /**
@@ -205,6 +272,32 @@
     }
 
     /**
+     * Returns VideoProvider of the conference. This can be null.
+     *
+     * @hide
+     */
+    @Override
+    public VideoProvider getVideoProvider() {
+        if (mConferenceHost != null) {
+            return mConferenceHost.getVideoProvider();
+        }
+        return null;
+    }
+
+    /**
+     * Returns video state of conference
+     *
+     * @hide
+     */
+    @Override
+    public int getVideoState() {
+        if (mConferenceHost != null) {
+            return mConferenceHost.getVideoState();
+        }
+        return VideoProfile.VideoState.AUDIO_ONLY;
+    }
+
+    /**
      * Invoked when the Conference and all its {@link Connection}s should be disconnected.
      * <p>
      * Hangs up the call via the conference host connection.  When the host connection has been
@@ -317,6 +410,16 @@
         // No-op
     }
 
+    private int applyCapability(int capabilities, int capability) {
+        int newCapabilities = capabilities | capability;
+        return newCapabilities;
+    }
+
+    private int removeCapability(int capabilities, int capability) {
+        int newCapabilities = capabilities & ~capability;
+        return newCapabilities;
+    }
+
     /**
      * Updates the manage conference capability of the conference.  Where there are one or more
      * conference event package participants, the conference management is permitted.  Where there
@@ -352,6 +455,7 @@
         mConferenceHost = conferenceHost;
         mConferenceHost.addConnectionListener(mConferenceHostListener);
         mConferenceHost.addTelephonyConnectionListener(mTelephonyConnectionListener);
+        setState(mConferenceHost.getState());
     }
 
     /**
diff --git a/src/com/android/services/telephony/PstnPhoneCapabilitiesNotifier.java b/src/com/android/services/telephony/PstnPhoneCapabilitiesNotifier.java
new file mode 100644
index 0000000..48568d3
--- /dev/null
+++ b/src/com/android/services/telephony/PstnPhoneCapabilitiesNotifier.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.services.telephony;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telecom.TelecomManager;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneProxy;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.util.Preconditions;
+import com.android.phone.PhoneUtils;
+
+/**
+ * Listens to phone's capabilities changed event and notifies Telecomm. One instance of these exists
+ * for each of the telephony-based call services.
+ */
+final class PstnPhoneCapabilitiesNotifier {
+    private static final int EVENT_VIDEO_CAPABILITIES_CHANGED = 1;
+
+    private final PhoneProxy mPhoneProxy;
+    private Phone mPhoneBase;
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case EVENT_VIDEO_CAPABILITIES_CHANGED:
+                    handleVideoCapabilitesChanged((AsyncResult) msg.obj);
+                    break;
+                default:
+                    break;
+            }
+        }
+    };
+
+    private final BroadcastReceiver mRatReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED.equals(action)) {
+                String newPhone = intent.getStringExtra(PhoneConstants.PHONE_NAME_KEY);
+                Log.d(this, "Radio technology switched. Now %s is active.", newPhone);
+
+                registerForNotifications();
+            }
+        }
+    };
+
+    /*package*/
+    PstnPhoneCapabilitiesNotifier(PhoneProxy phoneProxy) {
+        Preconditions.checkNotNull(phoneProxy);
+
+        mPhoneProxy = phoneProxy;
+
+        registerForNotifications();
+
+        IntentFilter intentFilter =
+                new IntentFilter(TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED);
+        mPhoneProxy.getContext().registerReceiver(mRatReceiver, intentFilter);
+    }
+
+    /*package*/
+    void teardown() {
+        unregisterForNotifications();
+        mPhoneProxy.getContext().unregisterReceiver(mRatReceiver);
+    }
+
+    private void registerForNotifications() {
+        Phone newPhone = mPhoneProxy.getActivePhone();
+        if (newPhone != mPhoneBase) {
+            unregisterForNotifications();
+
+            if (newPhone != null) {
+                Log.d(this, "Registering: " + newPhone);
+                mPhoneBase = newPhone;
+                mPhoneBase.registerForVideoCapabilityChanged(
+                        mHandler, EVENT_VIDEO_CAPABILITIES_CHANGED, null);
+            }
+        }
+    }
+
+    private void unregisterForNotifications() {
+        if (mPhoneBase != null) {
+            Log.d(this, "Unregistering: " + mPhoneBase);
+            mPhoneBase.unregisterForVideoCapabilityChanged(mHandler);
+        }
+    }
+
+    private void handleVideoCapabilitesChanged(AsyncResult ar) {
+        try {
+            boolean isVideoCapable = (Boolean) ar.result;
+            Log.d(this, "handleVideoCapabilitesChanged. Video capability - " + isVideoCapable);
+            PhoneAccountHandle accountHandle =
+                    PhoneUtils.makePstnPhoneAccountHandle(mPhoneProxy);
+            TelecomManager telecomMgr = TelecomManager.from(mPhoneProxy.getContext());
+            PhoneAccount oldPhoneAccount = telecomMgr.getPhoneAccount(accountHandle);
+            PhoneAccount.Builder builder = new PhoneAccount.Builder(oldPhoneAccount);
+
+            int capabilites = newCapabilities(oldPhoneAccount.getCapabilities(),
+                    PhoneAccount.CAPABILITY_VIDEO_CALLING, isVideoCapable);
+            builder.setCapabilities(capabilites);
+            telecomMgr.registerPhoneAccount(builder.build());
+        } catch (Exception e) {
+            Log.d(this, "handleVideoCapabilitesChanged. Exception=" + e);
+        }
+    }
+
+    private int newCapabilities(int capabilities, int capability, boolean set) {
+        if (set) {
+            capabilities |= capability;
+        } else {
+            capabilities &= ~capability;
+        }
+        return capabilities;
+    }
+}
diff --git a/src/com/android/services/telephony/TelecomAccountRegistry.java b/src/com/android/services/telephony/TelecomAccountRegistry.java
index 799d844..866118c 100644
--- a/src/com/android/services/telephony/TelecomAccountRegistry.java
+++ b/src/com/android/services/telephony/TelecomAccountRegistry.java
@@ -57,6 +57,7 @@
         private final Phone mPhone;
         private final PhoneAccount mAccount;
         private final PstnIncomingCallNotifier mIncomingCallNotifier;
+        private final PstnPhoneCapabilitiesNotifier mPhoneCapabilitiesNotifier;
 
         AccountEntry(Phone phone, boolean isEmergency, boolean isDummy) {
             mPhone = phone;
@@ -64,10 +65,12 @@
             Log.d(this, "Registered phoneAccount: %s with handle: %s",
                     mAccount, mAccount.getAccountHandle());
             mIncomingCallNotifier = new PstnIncomingCallNotifier((PhoneProxy) mPhone);
+            mPhoneCapabilitiesNotifier = new PstnPhoneCapabilitiesNotifier((PhoneProxy) mPhone);
         }
 
         void teardown() {
             mIncomingCallNotifier.teardown();
+            mPhoneCapabilitiesNotifier.teardown();
         }
 
         /**
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index d272252..1df63e7 100644
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -192,6 +192,17 @@
         public void onConferenceParticipantsChanged(List<ConferenceParticipant> participants) {
             updateConferenceParticipants(participants);
         }
+
+        /**
+         * Used by the {@link com.android.internal.telephony.Connection} to report a change in the
+         * substate of the current call
+         *
+         * @param callSubstate The call substate.
+         */
+        @Override
+        public void onCallSubstateChanged(int callSubstate) {
+            setCallSubstate(callSubstate);
+        }
     };
 
     private com.android.internal.telephony.Connection mOriginalConnection;
@@ -519,11 +530,13 @@
 
         // Set video state and capabilities
         setVideoState(mOriginalConnection.getVideoState());
+        updateState();
         setLocalVideoCapable(mOriginalConnection.isLocalVideoCapable());
         setRemoteVideoCapable(mOriginalConnection.isRemoteVideoCapable());
         setWifi(mOriginalConnection.isWifi());
         setVideoProvider(mOriginalConnection.getVideoProvider());
         setAudioQuality(mOriginalConnection.getAudioQuality());
+        setCallSubstate(mOriginalConnection.getCallSubstate());
 
         if (isImsConnection()) {
             mWasImsConnection = true;
@@ -677,6 +690,7 @@
                     break;
             }
         }
+        updateStatusHints();
         updateConnectionCapabilities();
         updateAddress();
         updateMultiparty();
@@ -876,9 +890,9 @@
     }
 
     private void updateStatusHints() {
-        if (mIsWifi && (mOriginalConnection.getState() == Call.State.INCOMING
-                || mOriginalConnection.getState() == Call.State.ACTIVE)) {
-            int labelId = mOriginalConnection.getState() == Call.State.INCOMING
+        boolean isIncoming = isValidRingingCall();
+        if (mIsWifi && (isIncoming || getState() == STATE_ACTIVE)) {
+            int labelId = isIncoming
                     ? R.string.status_hint_label_incoming_wifi_call
                     : R.string.status_hint_label_wifi_call;