Merge commit '8bef461' into merge_work

Change-Id: I4cfea114e24c0b0671e99118316ad839dafbe400
diff --git a/InCallUI/res/drawable/ic_lockscreen_answer_rx_video.xml b/InCallUI/res/drawable/ic_lockscreen_answer_rx_video.xml
new file mode 100644
index 0000000..c5a41d8
--- /dev/null
+++ b/InCallUI/res/drawable/ic_lockscreen_answer_rx_video.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (c) 2014, The Linux Foundation. All rights reserved.
+  ~ Not a Contribution.
+  ~ Copyright (C) 2014 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
+  -->
+<!-- Used with incoming call wigdet. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_enabled="true" android:state_active="false" android:state_focused="false"
+        android:drawable="@drawable/ic_lockscreen_answer_rx_video_normal_layer"/>
+    <item
+        android:state_enabled="true" android:state_active="true"  android:state_focused="false"
+        android:drawable="@drawable/ic_lockscreen_answer_rx_video_activated_layer" />
+    <item
+        android:state_enabled="true" android:state_active="false"  android:state_focused="true"
+        android:drawable="@drawable/ic_lockscreen_answer_rx_video_activated_layer" />
+</selector>
diff --git a/InCallUI/res/drawable/ic_lockscreen_answer_rx_video_activated_layer.xml b/InCallUI/res/drawable/ic_lockscreen_answer_rx_video_activated_layer.xml
new file mode 100644
index 0000000..750ef5e
--- /dev/null
+++ b/InCallUI/res/drawable/ic_lockscreen_answer_rx_video_activated_layer.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (c) 2014, The Linux Foundation. All rights reserved.
+  ~ Not a Contribution.
+  ~ Copyright (C) 2014 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
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/fab_red" />
+    <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@drawable/ic_rx_videocam"
+            android:tint="@color/glowpad_widget_active_color"
+            android:autoMirrored="true" />
+    </item>
+</layer-list>
diff --git a/InCallUI/res/drawable/ic_lockscreen_answer_rx_video_normal_layer.xml b/InCallUI/res/drawable/ic_lockscreen_answer_rx_video_normal_layer.xml
new file mode 100644
index 0000000..5efd3d1
--- /dev/null
+++ b/InCallUI/res/drawable/ic_lockscreen_answer_rx_video_normal_layer.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (c) 2014, The Linux Foundation. All rights reserved.
+  ~ Not a Contribution.
+  ~ Copyright (C) 2014 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
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- A fake circle to fix the size of this layer asset. -->
+    <item>
+        <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
+            <solid android:color="#00000000"/>
+            <size
+                android:width="@dimen/incoming_call_widget_circle_size"
+                android:height="@dimen/incoming_call_widget_circle_size" />
+        </shape>
+    </item>
+    <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@drawable/ic_rx_videocam"
+            android:tint="@color/glowpad_call_widget_normal_tint"
+            android:autoMirrored="true" />
+    </item>
+</layer-list>
diff --git a/InCallUI/res/drawable/ic_lockscreen_answer_tx_video.xml b/InCallUI/res/drawable/ic_lockscreen_answer_tx_video.xml
new file mode 100644
index 0000000..15d1197
--- /dev/null
+++ b/InCallUI/res/drawable/ic_lockscreen_answer_tx_video.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (c) 2014, The Linux Foundation. All rights reserved.
+  ~ Not a Contribution.
+  ~ Copyright (C) 2014 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
+  -->
+<!-- Used with incoming call wigdet. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item
+        android:state_enabled="true" android:state_active="false" android:state_focused="false"
+        android:drawable="@drawable/ic_lockscreen_answer_tx_video_normal_layer"/>
+    <item
+        android:state_enabled="true" android:state_active="true"  android:state_focused="false"
+        android:drawable="@drawable/ic_lockscreen_answer_tx_video_activated_layer" />
+    <item
+        android:state_enabled="true" android:state_active="false"  android:state_focused="true"
+        android:drawable="@drawable/ic_lockscreen_answer_tx_video_activated_layer" />
+</selector>
diff --git a/InCallUI/res/drawable/ic_lockscreen_answer_tx_video_activated_layer.xml b/InCallUI/res/drawable/ic_lockscreen_answer_tx_video_activated_layer.xml
new file mode 100644
index 0000000..c1dca4d
--- /dev/null
+++ b/InCallUI/res/drawable/ic_lockscreen_answer_tx_video_activated_layer.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (c) 2014, The Linux Foundation. All rights reserved.
+  ~ Not a Contribution.
+  ~ Copyright (C) 2014 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
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/fab_green" />
+    <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@drawable/ic_tx_videocam"
+            android:tint="@color/glowpad_widget_active_color"
+            android:autoMirrored="true" />
+    </item>
+</layer-list>
diff --git a/InCallUI/res/drawable/ic_lockscreen_answer_tx_video_normal_layer.xml b/InCallUI/res/drawable/ic_lockscreen_answer_tx_video_normal_layer.xml
new file mode 100644
index 0000000..b0ad943
--- /dev/null
+++ b/InCallUI/res/drawable/ic_lockscreen_answer_tx_video_normal_layer.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (c) 2014, The Linux Foundation. All rights reserved.
+  ~ Not a Contribution.
+  ~ Copyright (C) 2014 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
+  -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- A fake circle to fix the size of this layer asset. -->
+    <item>
+        <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
+            <solid android:color="#00000000"/>
+            <size
+                android:width="@dimen/incoming_call_widget_circle_size"
+                android:height="@dimen/incoming_call_widget_circle_size" />
+        </shape>
+    </item>
+    <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@drawable/ic_tx_videocam"
+            android:tint="@color/glowpad_call_widget_normal_tint"
+            android:autoMirrored="true" />
+    </item>
+</layer-list>
diff --git a/InCallUI/res/layout-land/call_card_fragment.xml b/InCallUI/res/layout-land/call_card_fragment.xml
index aa04840..7714178 100644
--- a/InCallUI/res/layout-land/call_card_fragment.xml
+++ b/InCallUI/res/layout-land/call_card_fragment.xml
@@ -81,6 +81,13 @@
         android:layout_height="wrap_content"
         android:layout_alignTop="@id/photo" />
 
+    <fragment android:name="com.android.incallui.VideoCallFragment"
+        android:layout_alignParentStart="true"
+        android:layout_gravity="start|center_vertical"
+        android:id="@+id/videoCallFragment"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
     <!-- Progress spinner, useful for indicating pending operations such as upgrade to video. -->
     <FrameLayout
         android:id="@+id/progressSpinner"
diff --git a/InCallUI/res/layout-land/video_call_views.xml b/InCallUI/res/layout-land/video_call_views.xml
index 7065d45..8961ea4 100644
--- a/InCallUI/res/layout-land/video_call_views.xml
+++ b/InCallUI/res/layout-land/video_call_views.xml
@@ -22,7 +22,7 @@
 
     <TextureView
         android:id="@+id/incomingVideo"
-        android:layout_gravity="center_horizontal"
+        android:layout_gravity="center"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
     <!-- The width and height are replaced at runtime based on the selected camera. -->
@@ -32,4 +32,4 @@
         android:layout_margin="@dimen/video_preview_margin"
         android:layout_width="70dp"
         android:layout_height="120dp" />
-</FrameLayout>
\ No newline at end of file
+</FrameLayout>
diff --git a/InCallUI/res/layout/call_button_fragment.xml b/InCallUI/res/layout/call_button_fragment.xml
index 18906dd..604d79e 100644
--- a/InCallUI/res/layout/call_button_fragment.xml
+++ b/InCallUI/res/layout/call_button_fragment.xml
@@ -159,6 +159,13 @@
             android:contentDescription="@string/onscreenOverflowText"
             android:visibility="gone" />
 
+        <!-- "Manage conference button (Video Call) " -->
+        <ImageButton android:id="@+id/manageVideoCallConferenceButton"
+            style="@style/InCallButton"
+            android:background="@drawable/ic_group_white_24dp"
+            android:contentDescription="@string/onscreenManageConferenceText"
+            android:visibility="gone" />
+
     </LinearLayout>
 
 </LinearLayout>
diff --git a/InCallUI/res/layout/call_card_fragment.xml b/InCallUI/res/layout/call_card_fragment.xml
index 17906f7..dc3ee11 100644
--- a/InCallUI/res/layout/call_card_fragment.xml
+++ b/InCallUI/res/layout/call_card_fragment.xml
@@ -72,6 +72,13 @@
         android:background="@android:color/white"
         android:src="@drawable/img_no_image_automirrored" />
 
+    <fragment android:name="com.android.incallui.VideoCallFragment"
+        android:id="@+id/videoCallFragment"
+        android:layout_alignParentTop="true"
+        android:layout_gravity="top|center_horizontal"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
     <!-- Progress spinner, useful for indicating pending operations such as upgrade to video. -->
     <FrameLayout
         android:id="@+id/progressSpinner"
diff --git a/InCallUI/res/layout/video_call_views.xml b/InCallUI/res/layout/video_call_views.xml
index ab03aa3..8961ea4 100644
--- a/InCallUI/res/layout/video_call_views.xml
+++ b/InCallUI/res/layout/video_call_views.xml
@@ -22,7 +22,7 @@
 
     <TextureView
         android:id="@+id/incomingVideo"
-        android:layout_gravity="center_vertical"
+        android:layout_gravity="center"
         android:layout_width="match_parent"
         android:layout_height="match_parent" />
     <!-- The width and height are replaced at runtime based on the selected camera. -->
@@ -32,4 +32,4 @@
         android:layout_margin="@dimen/video_preview_margin"
         android:layout_width="70dp"
         android:layout_height="120dp" />
-</FrameLayout>
\ No newline at end of file
+</FrameLayout>
diff --git a/InCallUI/res/menu/incall_overflow_menu.xml b/InCallUI/res/menu/incall_overflow_menu.xml
index 06208eb..2de8587 100644
--- a/InCallUI/res/menu/incall_overflow_menu.xml
+++ b/InCallUI/res/menu/incall_overflow_menu.xml
@@ -30,4 +30,7 @@
 
     <item android:id="@+id/overflow_swap_menu_item"
           android:title="@string/overflowSwapMenuItemText" />
+
+    <item android:id="@+id/overflow_manage_conference_menu_item"
+          android:title="@string/overflowManageConferenceMenuItemText" />
 </menu>
diff --git a/InCallUI/res/values/array.xml b/InCallUI/res/values/array.xml
index 5270de1..46592e1 100644
--- a/InCallUI/res/values/array.xml
+++ b/InCallUI/res/values/array.xml
@@ -74,6 +74,8 @@
         <item>@null</item>
         <item>@drawable/ic_lockscreen_decline</item>
         <item>@drawable/ic_lockscreen_answer_video</item>
+        <item>@drawable/ic_lockscreen_answer_tx_video</item>
+        <item>@drawable/ic_lockscreen_answer_rx_video</item>
     </array>
     <array name="incoming_call_widget_video_without_sms_target_descriptions">
         <item>@string/description_target_answer_video_call</item>
@@ -98,6 +100,8 @@
         <item>@drawable/ic_lockscreen_text</item>
         <item>@drawable/ic_lockscreen_decline</item>
         <item>@drawable/ic_lockscreen_answer</item>
+        <item>@drawable/ic_lockscreen_answer_tx_video</item>
+        <item>@drawable/ic_lockscreen_answer_rx_video</item>
     </array>
     <array name="incoming_call_widget_video_with_sms_target_descriptions">
         <item>@string/description_target_answer_video_call</item>
@@ -113,7 +117,7 @@
     </array>
 
 
-    <!-- For upgrade to video in an active video call.
+    <!-- For upgrade to video from VOLTE to VT (Tx/Rx/Bidirectional) in an active video call.
          - Accept upgrade to video request (drag right)
          - Decline upgrade to video request (drag left)
          - Answer as audio call (drag down) -->
@@ -121,7 +125,9 @@
         <item>@drawable/ic_lockscreen_answer_video</item>
         <item>@null</item>
         <item>@drawable/ic_lockscreen_decline</item>
-        <item>@null</item>"
+        <item>@drawable/ic_lockscreen_answer</item>
+        <item>@drawable/ic_lockscreen_answer_tx_video</item>
+        <item>@drawable/ic_lockscreen_answer_rx_video</item>
     </array>
     <array name="incoming_call_widget_video_upgrade_request_target_descriptions">
         <item>@string/description_target_accept_upgrade_to_video_request</item>
@@ -135,4 +141,37 @@
         <item>@string/description_direction_left</item>
         <item>@null</item>
     </array>
+
+    <!-- For accept/reject upgrade to video in active video call
+         - Accept upgrade to video request (drag right)
+         - Decline upgrade to video request (drag left)-->
+    <array name="incoming_call_widget_bidirectional_video_accept_reject_request_targets">
+        <item>@drawable/ic_lockscreen_answer_video</item>
+        <item>@drawable/ic_lockscreen_decline</item>
+    </array>
+
+    <!-- For accept/reject upgrade to video transmit in active video call
+         - Accept upgrade to video request (drag right)
+         - Decline upgrade to video request (drag left)-->
+    <array name="incoming_call_widget_video_transmit_accept_reject_request_targets">
+        <item>@drawable/ic_lockscreen_answer_tx_video</item>
+        <item>@drawable/ic_lockscreen_decline</item>
+    </array>
+    <array name="incoming_call_widget_video_transmit_request_target_descriptions">
+        <item>@string/description_target_accept_upgrade_to_video_transmit_request</item>
+        <item>@string/description_target_decline_upgrade_to_video_transmit_request</item>
+    </array>
+
+    <!-- For accept/reject upgrade to video receive in active video call
+         - Accept upgrade to video request (drag right)
+         - Decline upgrade to video request (drag left)-->
+    <array name="incoming_call_widget_video_receive_accept_reject_request_targets">
+        <item>@drawable/ic_lockscreen_answer_rx_video</item>
+        <item>@drawable/ic_lockscreen_decline</item>
+    </array>
+    <array name="incoming_call_widget_video_receive_request_target_descriptions">
+        <item>@string/description_target_accept_upgrade_to_video_receive_request</item>
+        <item>@string/description_target_decline_upgrade_to_video_receive_request</item>
+    </array>
+
 </resources>
diff --git a/InCallUI/res/values/strings.xml b/InCallUI/res/values/strings.xml
index 944eff6..99b8111 100644
--- a/InCallUI/res/values/strings.xml
+++ b/InCallUI/res/values/strings.xml
@@ -114,6 +114,8 @@
     <string name="card_title_video_call_requesting">Requesting video</string>
     <!-- In-call screen: status label when there is a problem connecting a video call. -->
     <string name="card_title_video_call_error">Can\'t connect video call</string>
+    <!-- In-call screen: status label when in a paused video call. -->
+    <string name="card_title_video_call_paused">Video call (Paused)</string>
 
     <!-- In-call screen: string shown to the user when their outgoing number is different than the
          number reported by TelephonyManager#getLine1Number() -->
@@ -263,6 +265,8 @@
     <string name="overflowMergeMenuItemText">Merge calls</string>
     <!-- Text for the onscreen "Swap calls" menu item. -->
     <string name="overflowSwapMenuItemText">Swap calls</string>
+    <!-- Text for the overflow "Manage Conference Video Call" menu item. -->
+    <string name="overflowManageConferenceMenuItemText">Manage Conference</string>
 
     <!-- Text for the onscreen "Hold" button -->
     <string name="onscreenHoldText">Hold</string>
@@ -299,6 +303,28 @@
     <!-- Text for the onscreen overflow button, to see additional actions which can be done. -->
     <string name="onscreenOverflowText">More options</string>
 
+    <!-- STOPSHIP -  For test only - In-call screen: Modify Call Options for IMS call -->
+    <string name="modify_call_option_title" translatable="false">Which type of call?</string>
+    <string name="modify_call_option_vt" translatable="false">Video bidirectional</string>
+    <string name="modify_call_option_vt_tx" translatable="false">Video transmit</string>
+    <string name="modify_call_option_vt_rx" translatable="false">Video receive</string>
+    <string name="modify_call_option_voice" translatable="false">Voice Only</string>
+
+    <!-- Message indicating video calls not allowed if user enabled TTY Mode -->
+    <string name="video_call_not_allowed_if_tty_enabled">Please disable TTY Mode to upgrade to video calls.</string>
+
+    <!-- Message indicating that Video Started flowing for IMS-VT calls -->
+    <string name="player_started">Player Started</string>
+    <!-- Message indicating that Video Stopped flowing for IMS-VT calls -->
+    <string name="player_stopped">Player Stopped</string>
+    <!-- Message indicating that camera failure has occurred for the selected camera and
+         as result camera is not ready -->
+    <string name="camera_not_ready">Camera not ready</string>
+    <!-- Message indicating that camera is ready/available -->
+    <string name="camera_ready">Camera ready</string>
+    <!-- Message indicating unknown call session event -->
+    <string name="unknown_call_session_event">"Unkown call session event"</string>
+
     <!-- For incoming calls, this is a string we can get from a CDMA network instead of
          the actual phone number, to indicate there's no number present.  DO NOT TRANSLATE. -->
     <string-array name="absent_num" translatable="false">
@@ -359,6 +385,18 @@
     <!-- Description of the target to decline a request to upgrade from an audio call to a video call.
          [CHAR LIMIT=NONE] -->
     <string name="description_target_decline_upgrade_to_video_request">Decline video request</string>
+    <!-- Description of the target to accept a request to upgrade from any call to a video transmit call.
+         [CHAR LIMIT=NONE] -->
+    <string name="description_target_accept_upgrade_to_video_transmit_request">Accept video transmit request</string>
+    <!-- Description of the target to decline a request to upgrade from any call to a video transmit call.
+         [CHAR LIMIT=NONE] -->
+    <string name="description_target_decline_upgrade_to_video_transmit_request">Decline video transmit request</string>
+        <!-- Description of the target to accept a request to upgrade from any call to a video receive call.
+         [CHAR LIMIT=NONE] -->
+    <string name="description_target_accept_upgrade_to_video_receive_request">Accept video receive request</string>
+    <!-- Description of the target to decline a request to upgrade from any call to a video receive call.
+         [CHAR LIMIT=NONE] -->
+    <string name="description_target_decline_upgrade_to_video_receive_request">Decline video receive request</string>
 
     <!-- Description of the up direction in which one can to slide the handle in the phone answer screen. [CHAR LIMIT=NONE] -->
     <string name="description_direction_up">Slide up for <xliff:g id="target_description" example="Unlock">%s</xliff:g>.</string>
@@ -417,4 +455,26 @@
 
     <!-- This can be used in any application wanting to disable the text "Emergency number" -->
     <string name="emergency_call_dialog_number_for_display">Emergency number</string>
+
+    <!-- STOPSHIP These strings are for debugging only --> 
+    <!-- Call substate label -->
+    <string name="call_substate_label" translatable="false">Call substate - \u000a</string>
+    <!-- Call substate label for call resumed -->
+    <string name="call_substate_call_resumed" translatable="false">Resumed \u000a</string>
+    <!-- Call substate label for call connected suspended (audio) -->
+    <string name="call_substate_connected_suspended_audio" translatable="false">Connected Suspended (Audio) \u000a</string>
+    <!-- Call substate label for call connected suspended (video) -->
+    <string name="call_substate_connected_suspended_video" translatable="false">Connected Suspended (Video) \u000a</string>
+    <!-- Call substate label for avp retry -->
+    <string name="call_substate_avp_retry" translatable="false">Avp Retry \u000a</string>
+    <!-- Video quality changed message -->
+    <string name="video_quality_changed" translatable="false">Video quality changed to \u0020</string>
+    <!-- Video quality High -->
+    <string name="video_quality_high" translatable="false">High</string>
+    <!-- Video quality Medium -->
+    <string name="video_quality_medium" translatable="false">Medium</string>
+    <!-- Video quality Low -->
+    <string name="video_quality_low" translatable="false">Low</string>
+    <!-- Video quality Unknown -->
+    <string name="video_quality_unknown" translatable="false">Unknown</string>
 </resources>
diff --git a/InCallUI/src/com/android/incallui/AnswerFragment.java b/InCallUI/src/com/android/incallui/AnswerFragment.java
index 8cbce10..d163637 100644
--- a/InCallUI/src/com/android/incallui/AnswerFragment.java
+++ b/InCallUI/src/com/android/incallui/AnswerFragment.java
@@ -50,6 +50,9 @@
     public static final int TARGET_SET_FOR_VIDEO_WITHOUT_SMS = 2;
     public static final int TARGET_SET_FOR_VIDEO_WITH_SMS = 3;
     public static final int TARGET_SET_FOR_VIDEO_UPGRADE_REQUEST = 4;
+    public static final int TARGET_SET_FOR_BIDIRECTIONAL_VIDEO_ACCEPT_REJECT_REQUEST = 5;
+    public static final int TARGET_SET_FOR_VIDEO_TRANSMIT_ACCEPT_REJECT_REQUEST = 6;
+    public static final int TARGET_SET_FOR_VIDEO_RECEIVE_ACCEPT_REJECT_REQUEST = 7;
 
     /**
      * The popup showing the list of canned responses.
@@ -161,6 +164,33 @@
                         .incoming_call_widget_video_upgrade_request_target_direction_descriptions;
                 handleDrawableResourceId = R.drawable.ic_incall_video_handle;
                 break;
+            case TARGET_SET_FOR_BIDIRECTIONAL_VIDEO_ACCEPT_REJECT_REQUEST:
+                targetResourceId =
+                    R.array.incoming_call_widget_bidirectional_video_accept_reject_request_targets;
+                targetDescriptionsResourceId =
+                        R.array.incoming_call_widget_video_upgrade_request_target_descriptions;
+                directionDescriptionsResourceId = R.array
+                        .incoming_call_widget_video_upgrade_request_target_direction_descriptions;
+                handleDrawableResourceId = R.drawable.ic_incall_video_handle;
+                break;
+            case TARGET_SET_FOR_VIDEO_TRANSMIT_ACCEPT_REJECT_REQUEST:
+                targetResourceId =
+                    R.array.incoming_call_widget_video_transmit_accept_reject_request_targets;
+                targetDescriptionsResourceId =
+                        R.array.incoming_call_widget_video_transmit_request_target_descriptions;
+                directionDescriptionsResourceId = R.array
+                        .incoming_call_widget_video_upgrade_request_target_direction_descriptions;
+                handleDrawableResourceId = R.drawable.ic_incall_video_handle;
+                break;
+            case TARGET_SET_FOR_VIDEO_RECEIVE_ACCEPT_REJECT_REQUEST:
+                targetResourceId =
+                    R.array.incoming_call_widget_video_receive_accept_reject_request_targets;
+                targetDescriptionsResourceId =
+                        R.array.incoming_call_widget_video_receive_request_target_descriptions;
+                directionDescriptionsResourceId = R.array
+                        .incoming_call_widget_video_upgrade_request_target_direction_descriptions;
+                handleDrawableResourceId = R.drawable.ic_incall_video_handle;
+                break;
             case TARGET_SET_FOR_AUDIO_WITHOUT_SMS:
             default:
                 targetResourceId = R.array.incoming_call_widget_audio_without_sms_targets;
@@ -336,12 +366,13 @@
 
     @Override
     public void onAnswer(int videoState, Context context) {
+        Log.d(this, "onAnswer videoState=" + videoState + " context=" + context);
         getPresenter().onAnswer(videoState, context);
     }
 
     @Override
-    public void onDecline() {
-        getPresenter().onDecline();
+    public void onDecline(Context context) {
+        getPresenter().onDecline(context);
     }
 
     @Override
diff --git a/InCallUI/src/com/android/incallui/AnswerPresenter.java b/InCallUI/src/com/android/incallui/AnswerPresenter.java
index cc95e01..32c7b9a 100644
--- a/InCallUI/src/com/android/incallui/AnswerPresenter.java
+++ b/InCallUI/src/com/android/incallui/AnswerPresenter.java
@@ -19,6 +19,8 @@
 import android.content.Context;
 
 import com.android.incallui.InCallPresenter.InCallState;
+import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
 
 import java.util.List;
 
@@ -51,7 +53,8 @@
                 processIncomingCall(call);
             }
             call = calls.getVideoUpgradeRequestCall();
-            if (call != null) {
+            Log.d(this, "getVideoUpgradeRequestCall call =" + call);
+            if (videoCall != null && call == null) {
                 processVideoUpgradeRequestCall(call);
             }
         } else {
@@ -72,6 +75,57 @@
         }
     }
 
+    @Override
+    public void onDisconnect(Call call) {
+        // no-op
+    }
+
+    @Override
+    public void onIncomingCall(Call call) {
+        // TODO: Ui is being destroyed when the fragment detaches.  Need clean up step to stop
+        // getting updates here.
+        Log.d(this, "onIncomingCall: " + this);
+        if (getUi() != null) {
+            Call modifyCall = CallList.getInstance().getVideoUpgradeRequestCall();
+            if (modifyCall != null) {
+                getUi().showAnswerUi(false);
+                Log.d(this, "declining upgrade request id: ");
+                CallList.getInstance().removeCallUpdateListener(mCallId, this);
+                InCallPresenter.getInstance().declineUpgradeRequest(getUi().getContext());
+            }
+            if (!call.getId().equals(mCallId)) {
+                // A new call is coming in.
+                processIncomingCall(call);
+            }
+>>>>>>> 8bef461
+        }
+    }
+
+    private boolean isVideoUpgradePending(Call call) {
+        return call.getSessionModificationState()
+                == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST;
+    }
+
+    @Override
+    public void onUpgradeToVideo(Call call) {
+        Log.d(this, "onUpgradeToVideo: " + this + " call=" + call);
+        if (getUi() == null) {
+            Log.d(this, "onUpgradeToVideo ui is null");
+            return;
+        }
+        boolean isUpgradePending = isVideoUpgradePending(call);
+        InCallPresenter inCallPresenter = InCallPresenter.getInstance();
+        if (isUpgradePending
+                && inCallPresenter.getInCallState() == InCallPresenter.InCallState.INCOMING) {
+            Log.d(this, "declining upgrade request");
+            //If there is incoming call reject upgrade request
+            inCallPresenter.declineUpgradeRequest(getUi().getContext());
+        } else if (isUpgradePending) {
+            Log.d(this, "process upgrade request as no MT call");
+            processVideoUpgradeRequestCall(call);
+        }
+    }
+
     private void processIncomingCall(Call call) {
         mCallId = call.getId();
         mCall = call;
@@ -97,28 +151,65 @@
     }
 
     private void processVideoUpgradeRequestCall(Call call) {
+        Log.d(this, " processVideoUpgradeRequestCall call=" + call);
         mCallId = call.getId();
         mCall = call;
 
         // Listen for call updates for the current call.
         CallList.getInstance().addCallUpdateListener(mCallId, this);
-        showAnswerUi(true);
 
-        getUi().showTargets(AnswerFragment.TARGET_SET_FOR_VIDEO_UPGRADE_REQUEST);
+        final int currentVideoState = call.getVideoState();
+        final int modifyToVideoState = call.getModifyToVideoState();
+
+        if (currentVideoState == modifyToVideoState) {
+            Log.w(this, "processVideoUpgradeRequestCall: Video states are same. Return.");
+            return;
+        }
+
+        showAnswerUi(true);
+        getUi().showTargets(getUiTarget(currentVideoState, modifyToVideoState));
+
+    }
+
+    private int getUiTarget(int currentVideoState, int modifyToVideoState) {
+        if (showVideoUpgradeOptions(currentVideoState, modifyToVideoState)) {
+            return AnswerFragment.TARGET_SET_FOR_VIDEO_UPGRADE_REQUEST;
+        } else if (isEnabled(modifyToVideoState, VideoProfile.VideoState.BIDIRECTIONAL)) {
+            return AnswerFragment.TARGET_SET_FOR_BIDIRECTIONAL_VIDEO_ACCEPT_REJECT_REQUEST;
+        }  else if (isEnabled(modifyToVideoState, VideoProfile.VideoState.TX_ENABLED)) {
+            return AnswerFragment.TARGET_SET_FOR_VIDEO_TRANSMIT_ACCEPT_REJECT_REQUEST;
+        }  else if (isEnabled(modifyToVideoState, VideoProfile.VideoState.RX_ENABLED)) {
+            return AnswerFragment.TARGET_SET_FOR_VIDEO_RECEIVE_ACCEPT_REJECT_REQUEST;
+        }
+        return AnswerFragment.TARGET_SET_FOR_VIDEO_UPGRADE_REQUEST;
+    }
+
+    private boolean showVideoUpgradeOptions(int currentVideoState, int modifyToVideoState) {
+        return currentVideoState == VideoProfile.VideoState.AUDIO_ONLY &&
+            isEnabled(modifyToVideoState, VideoProfile.VideoState.BIDIRECTIONAL);
+    }
+
+    private boolean isEnabled(int videoState, int mask) {
+        return (videoState & mask) == mask;
     }
 
     @Override
     public void onCallChanged(Call call) {
         Log.d(this, "onCallStateChange() " + call + " " + this);
         if (call.getState() != Call.State.INCOMING) {
-            // Stop listening for updates.
-            CallList.getInstance().removeCallUpdateListener(mCallId, this);
+            boolean isUpgradePending = isVideoUpgradePending(call);
+            if (!isUpgradePending) {
+                // Stop listening for updates.
+                CallList.getInstance().removeCallUpdateListener(mCallId, this);
+            }
 
-            showAnswerUi(false);
+            final Call incall = CallList.getInstance().getIncomingCall();
+            if (incall != null || isUpgradePending) {
+                showAnswerUi(true);
+            } else {
+                showAnswerUi(false);
+            }
 
-            // mCallId will hold the state of the call. We don't clear the mCall variable here as
-            // it may be useful for sending text messages after phone disconnects.
-            mCallId = null;
             mHasTextMessages = false;
         } else if (!mHasTextMessages) {
             final List<String> textMsgs = CallList.getInstance().getTextResponses(call.getId());
@@ -129,14 +220,14 @@
     }
 
     public void onAnswer(int videoState, Context context) {
+        Log.d(this, "onAnswer mCallId=" + mCallId + " videoState=" + videoState);
         if (mCallId == null) {
             return;
         }
 
-        Log.d(this, "onAnswer " + mCallId);
         if (mCall.getSessionModificationState()
                 == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
-            InCallPresenter.getInstance().acceptUpgradeRequest(context);
+            InCallPresenter.getInstance().acceptUpgradeRequest(videoState, context);
         } else {
             TelecomAdapter.getInstance().answerCall(mCall.getId(), videoState);
         }
@@ -146,9 +237,14 @@
      * TODO: We are using reject and decline interchangeably. We should settle on
      * reject since it seems to be more prevalent.
      */
-    public void onDecline() {
+    public void onDecline(Context context) {
         Log.d(this, "onDecline " + mCallId);
-        TelecomAdapter.getInstance().rejectCall(mCall.getId(), false, null);
+        if (mCall.getSessionModificationState()
+                == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
+            InCallPresenter.getInstance().declineUpgradeRequest(context);
+        } else {
+            TelecomAdapter.getInstance().rejectCall(mCall.getId(), false, null);
+        }
     }
 
     public void onText() {
diff --git a/InCallUI/src/com/android/incallui/Call.java b/InCallUI/src/com/android/incallui/Call.java
index d8e76c7..4ed7a49 100644
--- a/InCallUI/src/com/android/incallui/Call.java
+++ b/InCallUI/src/com/android/incallui/Call.java
@@ -18,8 +18,10 @@
 
 import com.android.contacts.common.CallUtil;
 import com.android.contacts.common.testing.NeededForTesting;
+import com.android.incallui.CallList.Listener;
 
 import android.content.Context;
+import android.hardware.camera2.CameraCharacteristics;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Trace;
@@ -122,8 +124,51 @@
         public static final int WAITING_FOR_RESPONSE = 1;
         public static final int REQUEST_FAILED = 2;
         public static final int RECEIVED_UPGRADE_TO_VIDEO_REQUEST = 3;
+        public static final int UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT = 4;
     }
 
+    public static class VideoSettings {
+        public static final int CAMERA_DIRECTION_UNKNOWN = -1;
+        public static final int CAMERA_DIRECTION_FRONT_FACING =
+                CameraCharacteristics.LENS_FACING_FRONT;
+        public static final int CAMERA_DIRECTION_BACK_FACING =
+                CameraCharacteristics.LENS_FACING_BACK;
+
+        private int mCameraDirection = CAMERA_DIRECTION_UNKNOWN;
+
+        /**
+         * Sets the camera direction. if camera direction is set to CAMERA_DIRECTION_UNKNOWN,
+         * the video state of the call should be used to infer the camera direction.
+         *
+         * @see {@link CameraCharacteristics#LENS_FACING_FRONT}
+         * @see {@link CameraCharacteristics#LENS_FACING_BACK}
+         */
+        public void setCameraDir(int cameraDirection) {
+            if (cameraDirection == CAMERA_DIRECTION_FRONT_FACING
+               || cameraDirection == CAMERA_DIRECTION_BACK_FACING) {
+                mCameraDirection = cameraDirection;
+            } else {
+                mCameraDirection = CAMERA_DIRECTION_UNKNOWN;
+            }
+        }
+
+        /**
+         * Gets the camera direction. if camera direction is set to CAMERA_DIRECTION_UNKNOWN,
+         * the video state of the call should be used to infer the camera direction.
+         *
+         * @see {@link CameraCharacteristics#LENS_FACING_FRONT}
+         * @see {@link CameraCharacteristics#LENS_FACING_BACK}
+         */
+        public int getCameraDir() {
+            return mCameraDirection;
+        }
+
+        public String toString() {
+            return "(CameraDir:" + getCameraDir() + ")";
+        }
+    }
+
+
     private static final String ID_PREFIX = Call.class.getSimpleName() + "_";
     private static int sIdCounter = 0;
 
@@ -131,12 +176,16 @@
             new android.telecom.Call.Listener() {
                 @Override
                 public void onStateChanged(android.telecom.Call call, int newState) {
+                    Log.d(this, "TelecommCallListener onStateChanged call=" + call + " newState="
+                            + newState);
                     update();
                 }
 
                 @Override
                 public void onParentChanged(android.telecom.Call call,
                         android.telecom.Call newParent) {
+                    Log.d(this, "TelecommCallListener onParentChanged call=" + call + " newParent="
+                            + newParent);
                     update();
                 }
 
@@ -149,29 +198,38 @@
                 @Override
                 public void onDetailsChanged(android.telecom.Call call,
                         android.telecom.Call.Details details) {
+                    Log.d(this, "TelecommCallListener onStateChanged call=" + call + " details="
+                            + details);
                     update();
                 }
 
                 @Override
                 public void onCannedTextResponsesLoaded(android.telecom.Call call,
                         List<String> cannedTextResponses) {
+                    Log.d(this, "TelecommCallListener onStateChanged call=" + call
+                            + " cannedTextResponses=" + cannedTextResponses);
                     update();
                 }
 
                 @Override
                 public void onPostDialWait(android.telecom.Call call,
                         String remainingPostDialSequence) {
+                    Log.d(this, "TelecommCallListener onStateChanged call=" + call
+                            + " remainingPostDialSequence=" + remainingPostDialSequence);
                     update();
                 }
 
                 @Override
                 public void onVideoCallChanged(android.telecom.Call call,
                         VideoCall videoCall) {
+                    Log.d(this, "TelecommCallListener onStateChanged call=" + call + " videoCall="
+                            + videoCall);
                     update();
                 }
 
                 @Override
                 public void onCallDestroyed(android.telecom.Call call) {
+                    Log.d(this, "TelecommCallListener onStateChanged call=" + call);
                     call.removeListener(mTelecommCallListener);
                 }
 
@@ -188,6 +246,11 @@
     private DisconnectCause mDisconnectCause;
     private int mSessionModificationState;
     private final List<String> mChildCallIds = new ArrayList<>();
+    private final VideoSettings mVideoSettings = new VideoSettings();
+    /**
+     * mModifyToVideoState is used to store requested upgrade / downgrade video state
+     */
+    private int mModifyToVideoState = VideoProfile.VideoState.AUDIO_ONLY;
 
     private InCallVideoCallListener mVideoCallListener;
 
@@ -212,6 +275,14 @@
         return mTelecommCall;
     }
 
+    /**
+     * @return video settings of the call, null if the call is not a video call.
+     * @see VideoProfile
+     */
+    public VideoSettings getVideoSettings() {
+        return mVideoSettings;
+    }
+
     private void update() {
         Trace.beginSection("Update");
         int oldState = getState();
@@ -225,7 +296,7 @@
     }
 
     private void updateFromTelecommCall() {
-        Log.d(this, "updateFromTelecommCall: " + mTelecommCall);
+        Log.d(this, "updateFromTelecommCall: " + mTelecommCall.toString());
         setState(translateState(mTelecommCall.getState()));
         setDisconnectCause(mTelecommCall.getDetails().getDisconnectCause());
 
@@ -395,24 +466,69 @@
         return mTelecommCall.getDetails().getVideoState();
     }
 
-    public boolean isVideoCall(Context context) {
-        // We want to show Video call buttons even if only one direction is enabled
-        // (That is what is happening when we receive a video call for example)
-        return CallUtil.isVideoEnabled(context) && (
-            VideoProfile.VideoState.isBidirectional(getVideoState()) ||
-            VideoProfile.VideoState.isReceptionEnabled(getVideoState()) ||
-            VideoProfile.VideoState.isTransmissionEnabled(getVideoState()));
+    public int getCallSubstate() {
+        return mTelecommCall.getDetails().getCallSubstate();
     }
 
+    public boolean isVideoCall(Context context) {
+        return CallUtil.isVideoEnabled(context) &&
+                VideoProfile.VideoState.isVideo(getVideoState());
+    }
+
+    /**
+     * This method is called when we request for a video upgrade or downgrade. This handles the
+     * session modification state RECEIVED_UPGRADE_TO_VIDEO_REQUEST and sets the video state we
+     * want to upgrade/downgrade to.
+     */
+    public void setSessionModificationTo(int videoState) {
+        Log.d(this, "setSessionModificationTo - video state= " + videoState);
+        if (videoState == getVideoState()) {
+            mSessionModificationState = Call.SessionModificationState.NO_REQUEST;
+            Log.w(this,"setSessionModificationTo - Clearing session modification state");
+        } else {
+            mSessionModificationState =
+                Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST;
+            setModifyToVideoState(videoState);
+            CallList.getInstance().onUpgradeToVideo(this);
+        }
+
+        Log.d(this, "setSessionModificationTo - mSessionModificationState="
+            + mSessionModificationState + " video state= " + videoState);
+        update();
+    }
+
+    /**
+     * This method is called to handle any other session modification states other than
+     * RECEIVED_UPGRADE_TO_VIDEO_REQUEST. We set the modification state and reset the video state
+     * when an upgrade request has been completed or failed.
+     */
     public void setSessionModificationState(int state) {
+        if (state == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
+            Log.e(this,
+            "setSessionModificationState not to be called for RECEIVED_UPGRADE_TO_VIDEO_REQUEST");
+            return;
+        }
+
         boolean hasChanged = mSessionModificationState != state;
         mSessionModificationState = state;
-
+        Log.d(this, "setSessionModificationState " + state + " mSessionModificationState="
+                + mSessionModificationState);
+        if (state != Call.SessionModificationState.WAITING_FOR_RESPONSE) {
+            setModifyToVideoState(VideoProfile.VideoState.AUDIO_ONLY);
+        }
         if (hasChanged) {
             update();
         }
     }
 
+    private void setModifyToVideoState(int newVideoState) {
+        mModifyToVideoState = newVideoState;
+    }
+
+    public int getModifyToVideoState() {
+        return mModifyToVideoState;
+    }
+
     public static boolean areSame(Call call1, Call call2) {
         if (call1 == null && call2 == null) {
             return true;
@@ -437,7 +553,7 @@
         }
 
         return String.format(Locale.US, "[%s, %s, %s, children:%s, parent:%s, conferenceable:%s, " +
-                "videoState:%d]",
+                "videoState:%d, callSubState:%d, mSessionModificationState:%d, VideoSettings:%s]",
                 mId,
                 State.toString(getState()),
                 android.telecom.Call.Details
@@ -445,6 +561,13 @@
                 mChildCallIds,
                 getParentId(),
                 this.mTelecommCall.getConferenceableCalls(),
-                mTelecommCall.getDetails().getVideoState());
+                mTelecommCall.getDetails().getVideoState(),
+                mTelecommCall.getDetails().getCallSubstate(), 
+                mSessionModificationState,
+                getVideoSettings());
+    }
+
+    public String toSimpleString() {
+        return super.toString();
     }
 }
diff --git a/InCallUI/src/com/android/incallui/CallButtonFragment.java b/InCallUI/src/com/android/incallui/CallButtonFragment.java
index 78956cb..a9fafae 100644
--- a/InCallUI/src/com/android/incallui/CallButtonFragment.java
+++ b/InCallUI/src/com/android/incallui/CallButtonFragment.java
@@ -16,7 +16,9 @@
 
 package com.android.incallui;
 
+import android.app.AlertDialog;
 import android.content.Context;
+import android.content.DialogInterface;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
@@ -26,6 +28,8 @@
 import android.graphics.drawable.StateListDrawable;
 import android.os.Bundle;
 import android.telecom.AudioState;
+import android.telecom.TelecomManager;
+import android.telecom.VideoProfile;
 import android.view.ContextThemeWrapper;
 import android.view.HapticFeedbackConstants;
 import android.view.LayoutInflater;
@@ -36,11 +40,13 @@
 import android.widget.CompoundButton;
 import android.widget.ImageButton;
 import android.widget.PopupMenu;
+import android.widget.Toast;
 import android.widget.PopupMenu.OnDismissListener;
 import android.widget.PopupMenu.OnMenuItemClickListener;
 
 import com.android.contacts.common.util.MaterialColorMapUtils;
 import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
+import java.util.ArrayList;
 
 /**
  * Fragment for call control buttons
@@ -50,6 +56,7 @@
         implements CallButtonPresenter.CallButtonUi, OnMenuItemClickListener, OnDismissListener,
         View.OnClickListener {
     private CompoundButton mAudioButton;
+    private static final int INVALID_INDEX = -1;
     private ImageButton mChangeToVoiceButton;
     private CompoundButton mMuteButton;
     private CompoundButton mShowDialpadButton;
@@ -61,6 +68,7 @@
     private ImageButton mMergeButton;
     private CompoundButton mPauseVideoButton;
     private ImageButton mOverflowButton;
+    private ImageButton mManageVideoCallConferenceButton;
 
     private PopupMenu mAudioModePopup;
     private boolean mAudioModePopupVisible;
@@ -120,7 +128,9 @@
         mPauseVideoButton.setOnClickListener(this);
         mOverflowButton = (ImageButton) parent.findViewById(R.id.overflowButton);
         mOverflowButton.setOnClickListener(this);
-
+        mManageVideoCallConferenceButton = (ImageButton) parent.findViewById(
+            R.id.manageVideoCallConferenceButton);
+        mManageVideoCallConferenceButton.setOnClickListener(this);
         return parent;
     }
 
@@ -156,7 +166,8 @@
                 getPresenter().addCallClicked();
                 break;
             case R.id.changeToVoiceButton:
-                getPresenter().changeToVoiceClicked();
+                // STOPSHIP One way video options
+                getPresenter().displayModifyCallOptions();
                 break;
             case R.id.muteButton: {
                 getPresenter().muteClicked(!mMuteButton.isSelected());
@@ -177,7 +188,8 @@
                 getPresenter().showDialpadClicked(!mShowDialpadButton.isSelected());
                 break;
             case R.id.changeToVideoButton:
-                getPresenter().changeToVideoClicked();
+                // STOPSHIP One way video options
+                getPresenter().displayModifyCallOptions();
                 break;
             case R.id.switchCameraButton:
                 getPresenter().switchCameraClicked(
@@ -190,6 +202,9 @@
             case R.id.overflowButton:
                 mOverflowPopup.show();
                 break;
+            case R.id.manageVideoCallConferenceButton:
+                onManageVideoCallConferenceClicked();
+                break;
             default:
                 isClickHandled = false;
                 Log.wtf(this, "onClick: unexpected");
@@ -325,6 +340,7 @@
         mMergeButton.setEnabled(isEnabled);
         mPauseVideoButton.setEnabled(isEnabled);
         mOverflowButton.setEnabled(isEnabled);
+        mManageVideoCallConferenceButton.setEnabled(isEnabled);
     }
 
     @Override
@@ -402,6 +418,10 @@
         mAddCallButton.setVisibility(show ? View.VISIBLE : View.GONE);
     }
 
+    public void showManageConferenceVideoCallButton(boolean show) {
+        mManageVideoCallConferenceButton.setVisibility(show ? View.VISIBLE : View.GONE);
+    }
+
     @Override
     public void showMergeButton(boolean show) {
         mMergeButton.setVisibility(show ? View.VISIBLE : View.GONE);
@@ -427,9 +447,83 @@
         mOverflowButton.setVisibility(show ? View.VISIBLE : View.GONE);
     }
 
+    /**The function is called when Modify Call button gets pressed. The function creates and
+     * displays modify call options.
+     */
+    public void displayModifyCallOptions() {
+        CallButtonPresenter.CallButtonUi ui = getUi();
+        if (ui == null) {
+            Log.e(this, "Cannot display ModifyCallOptions as ui is null");
+            return;
+        }
+
+        Context context = getContext();
+        if (isTtyModeEnabled()) {
+            Toast.makeText(context, context.getResources().getString(
+                    R.string.video_call_not_allowed_if_tty_enabled),
+                    Toast.LENGTH_SHORT).show();
+            return;
+        }
+
+        final ArrayList<CharSequence> items = new ArrayList<CharSequence>();
+        final ArrayList<Integer> itemToCallType = new ArrayList<Integer>();
+        final Resources res = ui.getContext().getResources();
+        // Prepare the string array and mapping.
+        items.add(res.getText(R.string.modify_call_option_voice));
+        itemToCallType.add(VideoProfile.VideoState.AUDIO_ONLY);
+
+        items.add(res.getText(R.string.modify_call_option_vt_rx));
+        itemToCallType.add(VideoProfile.VideoState.RX_ENABLED);
+
+        items.add(res.getText(R.string.modify_call_option_vt_tx));
+        itemToCallType.add(VideoProfile.VideoState.TX_ENABLED);
+
+        items.add(res.getText(R.string.modify_call_option_vt));
+        itemToCallType.add(VideoProfile.VideoState.BIDIRECTIONAL);
+
+        AlertDialog.Builder builder = new AlertDialog.Builder(getUi().getContext());
+        builder.setTitle(R.string.modify_call_option_title);
+        final AlertDialog alert;
+
+        DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int item) {
+                Toast.makeText(getUi().getContext(), items.get(item), Toast.LENGTH_SHORT).show();
+                final int selCallType = itemToCallType.get(item);
+                Log.v(this, "Videocall: ModifyCall: upgrade/downgrade to "
+                        + fromCallType(selCallType));
+                VideoProfile videoProfile = new VideoProfile(selCallType);
+                getPresenter().changeToVideoClicked(videoProfile);
+                dialog.dismiss();
+            }
+        };
+        int currVideoState = getPresenter().getCurrentVideoState();
+        int currUnpausedVideoState = CallUtils.getUnPausedVideoState(currVideoState);
+        int index = itemToCallType.indexOf(currUnpausedVideoState);
+        if (index == INVALID_INDEX) {
+            return;
+        }
+        builder.setSingleChoiceItems(items.toArray(new CharSequence[0]), index, listener);
+        alert = builder.create();
+        alert.show();
+    }
+
+    public static String fromCallType(int callType) {
+        switch (callType) {
+            case VideoProfile.VideoState.BIDIRECTIONAL:
+                return "VT";
+            case VideoProfile.VideoState.TX_ENABLED:
+                return "VT_TX";
+            case VideoProfile.VideoState.RX_ENABLED:
+                return "VT_RX";
+        }
+        return "";
+    }
+
     @Override
     public void configureOverflowMenu(boolean showMergeMenuOption, boolean showAddMenuOption,
-            boolean showHoldMenuOption, boolean showSwapMenuOption) {
+            boolean showHoldMenuOption, boolean showSwapMenuOption, 
+            boolean showManageConferenceVideoCallOption) {
         if (mOverflowPopup == null) {
             final ContextThemeWrapper contextWrapper = new ContextThemeWrapper(getActivity(),
                     R.style.InCallPopupMenuStyle);
@@ -455,6 +549,9 @@
                         case R.id.overflow_swap_menu_item:
                             getPresenter().addCallClicked();
                             break;
+                        case R.id.overflow_manage_conference_menu_item:
+                            onManageVideoCallConferenceClicked();
+                            break;
                         default:
                             Log.wtf(this, "onMenuItemClick: unexpected overflow menu click");
                             break;
@@ -478,6 +575,8 @@
         menu.findItem(R.id.overflow_resume_menu_item).setVisible(
                 showHoldMenuOption && mHoldButton.isSelected());
         menu.findItem(R.id.overflow_swap_menu_item).setVisible(showSwapMenuOption);
+        menu.findItem(R.id.overflow_manage_conference_menu_item).setVisible(
+            showManageConferenceVideoCallOption);
 
         mOverflowButton.setEnabled(menu.hasVisibleItems());
     }
@@ -557,6 +656,11 @@
         }
     }
 
+    private void onManageVideoCallConferenceClicked() {
+        Log.d(this, "onManageVideoCallConferenceClicked");
+        InCallPresenter.getInstance().showConferenceCallManager(true);
+    }
+
     /**
      * Refreshes the "Audio mode" popup if it's visible.  This is useful
      * (for example) when a wired headset is plugged or unplugged,
@@ -785,4 +889,11 @@
     public Context getContext() {
         return getActivity();
     }
+
+    private boolean isTtyModeEnabled() {
+        return (android.provider.Settings.Secure.getInt(
+                getContext().getContentResolver(),
+                android.provider.Settings.Secure.PREFERRED_TTY_MODE,
+                TelecomManager.TTY_MODE_OFF) != TelecomManager.TTY_MODE_OFF);
+    }
 }
diff --git a/InCallUI/src/com/android/incallui/CallButtonPresenter.java b/InCallUI/src/com/android/incallui/CallButtonPresenter.java
index 9fedc95..4e840be 100644
--- a/InCallUI/src/com/android/incallui/CallButtonPresenter.java
+++ b/InCallUI/src/com/android/incallui/CallButtonPresenter.java
@@ -16,6 +16,7 @@
 
 package com.android.incallui;
 
+import android.app.AlertDialog;
 import android.content.Context;
 import android.os.Bundle;
 import android.telecom.AudioState;
@@ -23,6 +24,7 @@
 import android.telecom.VideoProfile;
 
 import com.android.incallui.AudioModeProvider.AudioModeListener;
+import com.android.incallui.InCallCameraManager.Listener;
 import com.android.incallui.InCallPresenter.CanAddCallListener;
 import com.android.incallui.InCallPresenter.InCallState;
 import com.android.incallui.InCallPresenter.InCallStateListener;
@@ -36,7 +38,7 @@
  */
 public class CallButtonPresenter extends Presenter<CallButtonPresenter.CallButtonUi>
         implements InCallStateListener, AudioModeListener, IncomingCallListener,
-        InCallDetailsListener, CanAddCallListener {
+        InCallDetailsListener, CanAddCallListener, Listener {
 
     private static final String KEY_AUTOMATICALLY_MUTED = "incall_key_automatically_muted";
     private static final String KEY_PREVIOUS_MUTE_STATE = "incall_key_previous_mute_state";
@@ -44,6 +46,7 @@
     private Call mCall;
     private boolean mAutomaticallyMuted = false;
     private boolean mPreviousMuteState = false;
+    private static final int BUTTON_THRESOLD_TO_DISPLAY_OVERFLOW_MENU = 5;
 
     public CallButtonPresenter() {
     }
@@ -59,6 +62,7 @@
         InCallPresenter.getInstance().addIncomingCallListener(this);
         InCallPresenter.getInstance().addDetailsListener(this);
         InCallPresenter.getInstance().addCanAddCallListener(this);
+        InCallPresenter.getInstance().getInCallCameraManager().addCameraSelectionListener(this);
     }
 
     @Override
@@ -69,6 +73,7 @@
         AudioModeProvider.getInstance().removeListener(this);
         InCallPresenter.getInstance().removeIncomingCallListener(this);
         InCallPresenter.getInstance().removeDetailsListener(this);
+        InCallPresenter.getInstance().getInCallCameraManager().removeCameraSelectionListener(this);
     }
 
     @Override
@@ -248,16 +253,21 @@
         getUi().displayDialpad(checked /* show */, true /* animate */);
     }
 
-    public void changeToVideoClicked() {
+    public void displayModifyCallOptions() {
+        getUi().displayModifyCallOptions();
+    }
+
+    public int getCurrentVideoState() {
+        return mCall.getVideoState();
+    }
+
+    public void changeToVideoClicked(VideoProfile videoProfile) {
         VideoCall videoCall = mCall.getVideoCall();
         if (videoCall == null) {
             return;
         }
 
-        VideoProfile videoProfile =
-                new VideoProfile(VideoProfile.VideoState.BIDIRECTIONAL);
         videoCall.sendSessionModifyRequest(videoProfile);
-
         mCall.setSessionModificationState(Call.SessionModificationState.WAITING_FOR_RESPONSE);
     }
 
@@ -277,12 +287,16 @@
 
         String cameraId = cameraManager.getActiveCameraId();
         if (cameraId != null) {
+            final int cameraDir = cameraManager.isUsingFrontFacingCamera()
+                    ? Call.VideoSettings.CAMERA_DIRECTION_FRONT_FACING
+                    : Call.VideoSettings.CAMERA_DIRECTION_BACK_FACING;
+            mCall.getVideoSettings().setCameraDir(cameraDir);
             videoCall.setCamera(cameraId);
             videoCall.requestCameraCapabilities();
         }
-        getUi().setSwitchCameraButton(!useFrontFacingCamera);
     }
 
+
     /**
      * Stop or start client's video transmission.
      * @param pause True if pausing the local user's video, or false if starting the local user's
@@ -331,6 +345,10 @@
         ui.enableMute(call.can(android.telecom.Call.Details.CAPABILITY_MUTE));
     }
 
+    private static int toInteger(boolean b) {
+        return b ? 1 : 0;
+    }
+
     /**
      * Updates the buttons applicable for the UI.
      *
@@ -338,29 +356,17 @@
      * @param context The context.
      */
     private void updateCallButtons(Call call, Context context) {
-        if (call.isVideoCall(context)) {
+        if (CallUtils.isVideoCall(call)) {
             updateVideoCallButtons(call);
-        } else {
-            updateVoiceCallButtons(call);
         }
+        updateVoiceCallButtons(call);
     }
 
     private void updateVideoCallButtons(Call call) {
         Log.v(this, "Showing buttons for video call.");
         final CallButtonUi ui = getUi();
 
-        // Hide all voice-call-related buttons.
-        ui.showAudioButton(false);
-        ui.showDialpadButton(false);
-        ui.showHoldButton(false);
-        ui.showSwapButton(false);
-        ui.showChangeToVideoButton(false);
-        ui.showAddCallButton(false);
-        ui.showMergeButton(false);
-        ui.showOverflowButton(false);
-
         // Show all video-call-related buttons.
-        ui.showChangeToVoiceButton(true);
         ui.showSwitchCameraButton(true);
         ui.showPauseVideoButton(true);
 
@@ -392,6 +398,10 @@
                 android.telecom.Call.Details.CAPABILITY_SWAP_CONFERENCE));
         Log.v(this, "Show add call ", TelecomAdapter.getInstance().canAddCall());
         Log.v(this, "Show mute ", call.can(android.telecom.Call.Details.CAPABILITY_MUTE));
+        Log.v(this, "Show video call local:",
+                        call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL)
+                        + " remote: "
+                        + call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE));
 
         final boolean canAdd = TelecomAdapter.getInstance().canAddCall();
         final boolean enableHoldOption = call.can(android.telecom.Call.Details.CAPABILITY_HOLD);
@@ -401,11 +411,13 @@
         boolean canVideoCall = call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_LOCAL)
                 && call.can(android.telecom.Call.Details.CAPABILITY_SUPPORTS_VT_REMOTE);
         ui.showChangeToVideoButton(canVideoCall);
-        ui.enableChangeToVideoButton(!isCallOnHold);
 
         final boolean showMergeOption = call.can(
                 android.telecom.Call.Details.CAPABILITY_MERGE_CONFERENCE);
         final boolean showAddCallOption = canAdd;
+        final boolean showManageVideoCallConferenceOption = call.can(
+                android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE)
+                && CallUtils.isVideoCall(call);
 
         // Show either HOLD or SWAP, but not both. If neither HOLD or SWAP is available:
         //     (1) If the device normally can hold, show HOLD in a disabled state.
@@ -415,38 +427,55 @@
         final boolean showHoldOption = !showSwapOption && (enableHoldOption || supportHold);
 
         ui.setHold(isCallOnHold);
-        // If we show video upgrade and add/merge and hold/swap, the overflow menu is needed.
-        final boolean isVideoOverflowScenario = canVideoCall
-                && (showAddCallOption || showMergeOption) && (showHoldOption || showSwapOption);
-        // If we show hold/swap, add, and merge simultaneously, the overflow menu is needed.
-        final boolean isOverflowScenario =
-                (showHoldOption || showSwapOption) && showMergeOption && showAddCallOption;
+        //Initialize buttonCount = 2. Because speaker and dialpad these two always show in Call UI.
+        int buttonCount = 2;
+        buttonCount += toInteger(canVideoCall);
+        buttonCount += toInteger(showAddCallOption);
+        buttonCount += toInteger(showMergeOption);
+        buttonCount += toInteger(showHoldOption);
+        buttonCount += toInteger(showSwapOption);
+        buttonCount += toInteger(call.can(android.telecom.Call.Details.CAPABILITY_MUTE));
+        buttonCount += toInteger(showManageVideoCallConferenceOption);
+
+        Log.v(this, "show ManageVideoCallConference: " + showManageVideoCallConferenceOption);
+        Log.v(this, "No of InCall buttons: " + buttonCount + " canVideoCall: " + canVideoCall);
+
+        // Show overflow menu if number of buttons is greater than 5.
+        final boolean showOverflowMenu =
+                buttonCount > BUTTON_THRESOLD_TO_DISPLAY_OVERFLOW_MENU;
+        final boolean isVideoOverflowScenario = canVideoCall && showOverflowMenu;
+        final boolean isOverflowScenario = !canVideoCall && showOverflowMenu;
 
         if (isVideoOverflowScenario) {
             ui.showHoldButton(false);
             ui.showSwapButton(false);
             ui.showAddCallButton(false);
             ui.showMergeButton(false);
+            ui.showManageConferenceVideoCallButton(false);
 
             ui.configureOverflowMenu(
                     showMergeOption,
                     showAddCallOption /* showAddMenuOption */,
                     showHoldOption && enableHoldOption /* showHoldMenuOption */,
-                    showSwapOption);
+                    showSwapOption,
+                    showManageVideoCallConferenceOption);
             ui.showOverflowButton(true);
         } else {
             if (isOverflowScenario) {
                 ui.showAddCallButton(false);
                 ui.showMergeButton(false);
+                ui.showManageConferenceVideoCallButton(false);
 
                 ui.configureOverflowMenu(
                         showMergeOption,
                         showAddCallOption /* showAddMenuOption */,
                         false /* showHoldMenuOption */,
-                        false /* showSwapMenuOption */);
+                        false /* showSwapMenuOption */,
+                        showManageVideoCallConferenceOption);
             } else {
                 ui.showMergeButton(showMergeOption);
                 ui.showAddCallButton(showAddCallOption);
+                ui.showManageConferenceVideoCallButton(showManageVideoCallConferenceOption);
             }
 
             ui.showOverflowButton(isOverflowScenario);
@@ -500,16 +529,27 @@
         void showSwitchCameraButton(boolean show);
         void setSwitchCameraButton(boolean isBackFacingCamera);
         void showAddCallButton(boolean show);
+        void showManageConferenceVideoCallButton(boolean show);
         void showMergeButton(boolean show);
         void showPauseVideoButton(boolean show);
         void setPauseVideoButton(boolean isPaused);
         void showOverflowButton(boolean show);
         void displayDialpad(boolean on, boolean animate);
+        void displayModifyCallOptions();
         boolean isDialpadVisible();
         void setAudio(int mode);
         void setSupportedAudio(int mask);
         void configureOverflowMenu(boolean showMergeMenuOption, boolean showAddMenuOption,
-                boolean showHoldMenuOption, boolean showSwapMenuOption);
+                boolean showHoldMenuOption, boolean showSwapMenuOption,
+                boolean showManageConferenceVideoCallOption);
         Context getContext();
     }
+
+    @Override
+    public void onActiveCameraSelectionChanged(boolean isUsingFrontFacingCamera) {
+        if (getUi() == null) {
+            return;
+        }
+        getUi().setSwitchCameraButton(!isUsingFrontFacingCamera);
+    }
 }
diff --git a/InCallUI/src/com/android/incallui/CallCardFragment.java b/InCallUI/src/com/android/incallui/CallCardFragment.java
index af58876..7420ba9 100644
--- a/InCallUI/src/com/android/incallui/CallCardFragment.java
+++ b/InCallUI/src/com/android/incallui/CallCardFragment.java
@@ -564,7 +564,7 @@
             mCallStateIcon.setVisibility(View.GONE);
         }
 
-        if (VideoProfile.VideoState.isBidirectional(videoState)
+        if (VideoProfile.VideoState.isVideo(videoState)
                 || (state == Call.State.ACTIVE && sessionModificationState
                         == Call.SessionModificationState.WAITING_FOR_RESPONSE)) {
             mCallStateVideoCallIcon.setVisibility(View.VISIBLE);
@@ -694,6 +694,9 @@
                 } else if (sessionModificationState
                         == Call.SessionModificationState.WAITING_FOR_RESPONSE) {
                     callStateLabel = context.getString(R.string.card_title_video_call_requesting);
+                } else if (VideoProfile.VideoState.isVideo(videoState) &&
+                        VideoProfile.VideoState.isPaused(videoState)) {
+                    callStateLabel = context.getString(R.string.card_title_video_call_paused);
                 } else if (VideoProfile.VideoState.isBidirectional(videoState)) {
                     callStateLabel = context.getString(R.string.card_title_video_call);
                 }
diff --git a/InCallUI/src/com/android/incallui/CallCardPresenter.java b/InCallUI/src/com/android/incallui/CallCardPresenter.java
index 9e5c611..bd36943 100644
--- a/InCallUI/src/com/android/incallui/CallCardPresenter.java
+++ b/InCallUI/src/com/android/incallui/CallCardPresenter.java
@@ -326,7 +326,8 @@
             return false;
         }
 
-        return mPrimary.can(Details.CAPABILITY_MANAGE_CONFERENCE);
+        return mPrimary.can(android.telecom.Call.Details.CAPABILITY_MANAGE_CONFERENCE)
+                && !mPrimary.isVideoCall(mContext);
     }
 
     private void setCallbackNumber() {
diff --git a/InCallUI/src/com/android/incallui/CallList.java b/InCallUI/src/com/android/incallui/CallList.java
index 1ede89d..9868aef 100644
--- a/InCallUI/src/com/android/incallui/CallList.java
+++ b/InCallUI/src/com/android/incallui/CallList.java
@@ -75,7 +75,9 @@
         public void onCallAdded(Phone phone, android.telecom.Call telecommCall) {
             Trace.beginSection("onCallAdded");
             Call call = new Call(telecommCall);
-            if (call.getState() == Call.State.INCOMING) {
+            Log.d(this, "onCallAdded: callState=" + call.getState());
+            if (call.getState() == Call.State.INCOMING ||
+                    call.getState() == Call.State.CALL_WAITING) {
                 onIncoming(call, call.getCannedSmsResponses());
             } else {
                 onUpdate(call);
@@ -141,6 +143,12 @@
         }
     }
 
+    public void onUpgradeToVideo(Call call){
+        Log.d(this, "onUpgradeToVideo call=" + call);
+        for (Listener listener : mListeners) {
+            listener.onUpgradeToVideo(call);
+        }
+    }
     /**
      * Called when a single call has changed.
      */
@@ -547,7 +555,11 @@
          * incoming calls.
          */
         public void onIncomingCall(Call call);
-
+        /**
+         * Called when a new modify call request comes in
+         * This is the only method that gets called for modify requests.
+         */
+        public void onUpgradeToVideo(Call call);
         /**
          * Called anytime there are changes to the call list.  The change can be switching call
          * states, updating information, etc. This method will NOT be called for new incoming
diff --git a/InCallUI/src/com/android/incallui/CallUtils.java b/InCallUI/src/com/android/incallui/CallUtils.java
new file mode 100644
index 0000000..80b553a
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/CallUtils.java
@@ -0,0 +1,90 @@
+/* Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux Foundation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.incallui;
+
+import android.telecom.VideoProfile;
+
+import com.google.common.base.Preconditions;
+
+public class CallUtils {
+
+    public static boolean isVideoCall(Call call) {
+        return call != null && VideoProfile.VideoState.isVideo(call.getVideoState());
+    }
+
+    public static boolean isIncomingVideoCall(Call call) {
+        if (!CallUtils.isVideoCall(call)) {
+            return false;
+        }
+        final int state = call.getState();
+        return (state == Call.State.INCOMING) || (state == Call.State.CALL_WAITING);
+    }
+
+    public static boolean isActiveVideoCall(Call call) {
+        return CallUtils.isVideoCall(call) && call.getState() == Call.State.ACTIVE;
+    }
+
+    public static boolean isOutgoingVideoCall(Call call) {
+        if (!CallUtils.isVideoCall(call)) {
+            return false;
+        }
+        final int state = call.getState();
+        return Call.State.isDialing(state) || state == Call.State.CONNECTING
+                || state == Call.State.PRE_DIAL_WAIT;
+    }
+
+    public static boolean isAudioCall(Call call) {
+        return call != null && VideoProfile.VideoState.isAudioOnly(call.getVideoState());
+    }
+
+    // TODO (ims-vt) Check if special handling is needed for CONF calls.
+    public static boolean canVideoPause(Call call) {
+        return isVideoCall(call) && call.getState() == Call.State.ACTIVE;
+    }
+
+    public static VideoProfile makeVideoPauseProfile(Call call) {
+        Preconditions.checkNotNull(call);
+        Preconditions.checkState(!VideoProfile.VideoState.isAudioOnly(call.getVideoState()));
+        return new VideoProfile(getPausedVideoState(call.getVideoState()));
+    }
+
+    public static VideoProfile makeVideoUnPauseProfile(Call call) {
+        Preconditions.checkNotNull(call);
+        return new VideoProfile(getUnPausedVideoState(call.getVideoState()));
+    }
+
+    public static int getUnPausedVideoState(int videoState) {
+        return videoState & (~VideoProfile.VideoState.PAUSED);
+    }
+
+    public static int getPausedVideoState(int videoState) {
+        return videoState | VideoProfile.VideoState.PAUSED;
+    }
+
+}
diff --git a/InCallUI/src/com/android/incallui/GlowPadWrapper.java b/InCallUI/src/com/android/incallui/GlowPadWrapper.java
index b50fdd8..584ce65 100644
--- a/InCallUI/src/com/android/incallui/GlowPadWrapper.java
+++ b/InCallUI/src/com/android/incallui/GlowPadWrapper.java
@@ -108,7 +108,7 @@
 
     @Override
     public void onTrigger(View v, int target) {
-        Log.d(this, "onTrigger()");
+        Log.d(this, "onTrigger() view=" + v + " target=" + target);
         final int resId = getResourceIdForTarget(target);
         switch (resId) {
             case R.drawable.ic_lockscreen_answer:
@@ -116,7 +116,7 @@
                 mTargetTriggered = true;
                 break;
             case R.drawable.ic_lockscreen_decline:
-                mAnswerListener.onDecline();
+                mAnswerListener.onDecline(getContext());
                 mTargetTriggered = true;
                 break;
             case R.drawable.ic_lockscreen_text:
@@ -128,6 +128,14 @@
                 mAnswerListener.onAnswer(VideoProfile.VideoState.BIDIRECTIONAL, getContext());
                 mTargetTriggered = true;
                 break;
+            case R.drawable.ic_lockscreen_answer_tx_video:
+                mAnswerListener.onAnswer(VideoProfile.VideoState.TX_ENABLED, getContext());
+                mTargetTriggered = true;
+                break;
+            case R.drawable.ic_lockscreen_answer_rx_video:
+                mAnswerListener.onAnswer(VideoProfile.VideoState.RX_ENABLED, getContext());
+                mTargetTriggered = true;
+                break;
             case R.drawable.ic_toolbar_video_off:
                 InCallPresenter.getInstance().declineUpgradeRequest(getContext());
                 mTargetTriggered = true;
@@ -154,7 +162,7 @@
 
     public interface AnswerListener {
         void onAnswer(int videoState, Context context);
-        void onDecline();
+        void onDecline(Context context);
         void onText();
     }
 }
diff --git a/InCallUI/src/com/android/incallui/InCallActivity.java b/InCallUI/src/com/android/incallui/InCallActivity.java
index dd49e07..1283177 100644
--- a/InCallUI/src/com/android/incallui/InCallActivity.java
+++ b/InCallUI/src/com/android/incallui/InCallActivity.java
@@ -123,10 +123,9 @@
     };
 
     /**
-     * Stores the current orientation of the activity.  Used to determine if a change in orientation
-     * has occurred.
+     * Used to determine if a change in orientation has occurred.
      */
-    private int mCurrentOrientation;
+    private static int sCurrentOrientation = Configuration.ORIENTATION_UNDEFINED;
 
     @Override
     protected void onCreate(Bundle icicle) {
@@ -158,9 +157,8 @@
 
         internalResolveIntent(getIntent());
 
-        mCurrentOrientation = getResources().getConfiguration().orientation;
-        mIsLandscape = getResources().getConfiguration().orientation
-                == Configuration.ORIENTATION_LANDSCAPE;
+        mIsLandscape = getResources().getConfiguration().orientation ==
+                Configuration.ORIENTATION_LANDSCAPE;
 
         final boolean isRtl = TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) ==
                 View.LAYOUT_DIRECTION_RTL;
@@ -216,6 +214,11 @@
 
         // setting activity should be last thing in setup process
         InCallPresenter.getInstance().setActivity(this);
+
+        // It is possible that the activity restarted because orientation changed.
+        // Notify listeners if orientation changed.
+        doOrientationChanged(getResources().getConfiguration().orientation);
+        InCallPresenter.getInstance().onActivityStarted();
     }
 
     @Override
@@ -267,6 +270,9 @@
     @Override
     protected void onStop() {
         Log.d(this, "onStop()...");
+
+        InCallPresenter.getInstance().updateIsChangingConfigurations();
+        InCallPresenter.getInstance().onActivityStopped();
         super.onStop();
     }
 
@@ -274,6 +280,7 @@
     protected void onDestroy() {
         Log.d(this, "onDestroy()...  this = " + this);
         InCallPresenter.getInstance().unsetActivity(this);
+        InCallPresenter.getInstance().updateIsChangingConfigurations();
         super.onDestroy();
     }
 
@@ -471,15 +478,22 @@
         InCallPresenter.getInstance().getProximitySensor().onConfigurationChanged(config);
         Log.d(this, "onConfigurationChanged "+config.orientation);
 
+        doOrientationChanged(config.orientation);
+        super.onConfigurationChanged(config);
+    }
+
+
+    private void doOrientationChanged(int orientation) {
+        Log.d(this, "doOrientationChanged prevOrientation=" + sCurrentOrientation +
+                " newOrientation=" + orientation);
         // Check to see if the orientation changed to prevent triggering orientation change events
         // for other configuration changes.
-        if (config.orientation != mCurrentOrientation) {
-            mCurrentOrientation = config.orientation;
+        if (orientation != sCurrentOrientation) {
+            sCurrentOrientation = orientation;
             InCallPresenter.getInstance().onDeviceRotationChange(
                     getWindowManager().getDefaultDisplay().getRotation());
-            InCallPresenter.getInstance().onDeviceOrientationChange(mCurrentOrientation);
+            InCallPresenter.getInstance().onDeviceOrientationChange(sCurrentOrientation);
         }
-        super.onConfigurationChanged(config);
     }
 
     public CallButtonFragment getCallButtonFragment() {
diff --git a/InCallUI/src/com/android/incallui/InCallApp.java b/InCallUI/src/com/android/incallui/InCallApp.java
index d6f4f42..a273d78 100644
--- a/InCallUI/src/com/android/incallui/InCallApp.java
+++ b/InCallUI/src/com/android/incallui/InCallApp.java
@@ -81,7 +81,9 @@
             } else if (action.equals(ACTION_HANG_UP_ONGOING_CALL)) {
                 InCallPresenter.getInstance().hangUpOngoingCall(context);
             } else if (action.equals(ACTION_ACCEPT_VIDEO_UPGRADE_REQUEST)) {
-                InCallPresenter.getInstance().acceptUpgradeRequest(context);
+                //TODO: Change calltype after adding support for TX and RX
+                InCallPresenter.getInstance().acceptUpgradeRequest(
+                        VideoProfile.VideoState.BIDIRECTIONAL, context);
             } else if (action.equals(ACTION_DECLINE_VIDEO_UPGRADE_REQUEST)) {
                 InCallPresenter.getInstance().declineUpgradeRequest(context);
             }
diff --git a/InCallUI/src/com/android/incallui/InCallCameraManager.java b/InCallUI/src/com/android/incallui/InCallCameraManager.java
index ded9387..b7ec079 100644
--- a/InCallUI/src/com/android/incallui/InCallCameraManager.java
+++ b/InCallUI/src/com/android/incallui/InCallCameraManager.java
@@ -25,12 +25,22 @@
 import android.util.Size;
 
 import java.lang.String;
+import java.util.Collections;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.Set;
 
 /**
  * Used to track which camera is used for outgoing video.
  */
 public class InCallCameraManager {
 
+    public interface Listener {
+        void onActiveCameraSelectionChanged(boolean isUsingFrontFacingCamera);
+    }
+
+    private final Set<Listener> mCameraSelectionListeners = Collections.
+        newSetFromMap(new ConcurrentHashMap<Listener, Boolean>(8,0.9f,1));
+
     /**
      * The camera ID for the front facing camera.
      */
@@ -73,6 +83,9 @@
      */
     public void setUseFrontFacingCamera(boolean useFrontFacingCamera) {
         mUseFrontFacingCamera = useFrontFacingCamera;
+        for (Listener listener : mCameraSelectionListeners) {
+            listener.onActiveCameraSelectionChanged(mUseFrontFacingCamera);
+        }
     }
 
     /**
@@ -148,4 +161,16 @@
             }
         }
     }
+
+    public void addCameraSelectionListener(Listener listener) {
+        if (listener != null) {
+            mCameraSelectionListeners.add(listener);
+        }
+    }
+
+    public void removeCameraSelectionListener(Listener listener) {
+        if (listener != null) {
+            mCameraSelectionListeners.remove(listener);
+        }
+    }
 }
diff --git a/InCallUI/src/com/android/incallui/InCallPresenter.java b/InCallUI/src/com/android/incallui/InCallPresenter.java
index 05a05a6..fdf1933 100644
--- a/InCallUI/src/com/android/incallui/InCallPresenter.java
+++ b/InCallUI/src/com/android/incallui/InCallPresenter.java
@@ -33,6 +33,8 @@
 import android.text.TextUtils;
 import android.view.Surface;
 import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
 
 import com.google.common.base.Preconditions;
 
@@ -175,6 +177,14 @@
      */
     private boolean mServiceBound = false;
 
+    /**
+     * When configuration changes Android kills the current activity and starts a new one.
+     * The flag is used to check if full clean up is necessary (activity is stopped and new 
+     * activity won't be started), or if a new activity will be started right after the current one
+     * is destroyed, and therefore no need in release all resources.
+     */
+    private boolean mIsChangingConfigurations = false;
+
     private Phone mPhone;
 
     /** Display colors for the UI. Consists of a primary color and secondary (darker) color */
@@ -251,6 +261,8 @@
         // will kick off an update and the whole process can start.
         mCallList.addListener(this);
 
+        VideoPauseController.getInstance().setUp(this);
+
         Log.d(this, "Finished InCallPresenter.setUp");
     }
 
@@ -266,6 +278,8 @@
         Log.d(this, "tearDown");
         mServiceConnected = false;
         attemptCleanup();
+
+        VideoPauseController.getInstance().tearDown();
     }
 
     private void attemptFinishActivity() {
@@ -411,7 +425,9 @@
 
         InCallState newState = getPotentialStateFromCallList(callList);
         InCallState oldState = mInCallState;
+        Log.d(this, "onCallListChange oldState= " + oldState + " newState=" + newState);
         newState = startOrFinishUi(newState);
+        Log.d(this, "onCallListChange newState changed to " + newState);
 
         // Set the new state before announcing it to the world
         Log.i(this, "Phone switching state: " + oldState + " -> " + newState);
@@ -448,6 +464,10 @@
         }
     }
 
+    @Override
+    public void onUpgradeToVideo(Call call) {
+        //NO-OP
+    }
     /**
      * Called when a call becomes disconnected. Called everytime an existing call
      * changes from being connected (incoming/outgoing/active) to disconnected.
@@ -683,33 +703,36 @@
         }
     }
 
-    public void acceptUpgradeRequest(Context context) {
+    public void acceptUpgradeRequest(int videoState, Context context) {
+        Log.d(this, " acceptUpgradeRequest videoState " + videoState);
         // Bail if we have been shut down and the call list is null.
         if (mCallList == null) {
             StatusBarNotifier.clearInCallNotification(context);
+            Log.e(this, " acceptUpgradeRequest mCallList is empty so returning");
             return;
         }
 
         Call call = mCallList.getVideoUpgradeRequestCall();
         if (call != null) {
-            VideoProfile videoProfile =
-                    new VideoProfile(VideoProfile.VideoState.BIDIRECTIONAL);
+            VideoProfile videoProfile = new VideoProfile(videoState);
             call.getVideoCall().sendSessionModifyResponse(videoProfile);
             call.setSessionModificationState(Call.SessionModificationState.NO_REQUEST);
         }
     }
 
     public void declineUpgradeRequest(Context context) {
+        Log.d(this, " declineUpgradeRequest");
         // Bail if we have been shut down and the call list is null.
         if (mCallList == null) {
             StatusBarNotifier.clearInCallNotification(context);
+            Log.e(this, " declineUpgradeRequest mCallList is empty so returning");
             return;
         }
 
         Call call = mCallList.getVideoUpgradeRequestCall();
         if (call != null) {
             VideoProfile videoProfile =
-                    new VideoProfile(VideoProfile.VideoState.AUDIO_ONLY);
+                    new VideoProfile(call.getVideoState());
             call.getVideoCall().sendSessionModifyResponse(videoProfile);
             call.setSessionModificationState(Call.SessionModificationState.NO_REQUEST);
         }
@@ -737,6 +760,20 @@
         return mIsActivityPreviouslyStarted;
     }
 
+    public boolean isChangingConfigurations() {
+        return mIsChangingConfigurations;
+    }
+
+    /*package*/
+    void updateIsChangingConfigurations() {
+        mIsChangingConfigurations = false;
+        if (mInCallActivity != null) {
+            mIsChangingConfigurations = mInCallActivity.isChangingConfigurations();
+        }
+        Log.d(this, "IsChangingConfigurations=" + mIsChangingConfigurations);
+    }
+
+
     /**
      * Called when the activity goes in/out of the foreground.
      */
@@ -766,6 +803,8 @@
 
         if (showing) {
             mIsActivityPreviouslyStarted = true;
+        } else {
+            updateIsChangingConfigurations();
         }
 
         for (InCallUiListener listener : mInCallUiListeners) {
@@ -781,6 +820,26 @@
         return mInCallUiListeners.remove(listener);
     }
 
+    /*package*/
+    void onActivityStarted() {
+        Log.d(this, "onActivityStarted");
+        notifyVideoPauseController(true);
+    }
+
+    /*package*/
+    void onActivityStopped() {
+        Log.d(this, "onActivityStopped");
+        notifyVideoPauseController(false);
+    }
+
+    private void notifyVideoPauseController(boolean showing) {
+        Log.d(this, "notifyVideoPauseController: mIsChangingConfigurations=" +
+                    mIsChangingConfigurations);
+        if (!mIsChangingConfigurations) {
+            VideoPauseController.getInstance().onUiShowing(showing);
+        }
+    }
+
     /**
      * Brings the app into the foreground if possible.
      */
@@ -1130,6 +1189,7 @@
 
         if (shouldCleanup) {
             mIsActivityPreviouslyStarted = false;
+            mIsChangingConfigurations = false;
 
             // blow away stale contact info so that we get fresh data on
             // the next set of calls
@@ -1161,6 +1221,10 @@
 
             mListeners.clear();
             mIncomingCallListeners.clear();
+            mDetailsListeners.clear();
+            mCanAddCallListeners.clear();
+            mOrientationListeners.clear();
+            mInCallEventListeners.clear();
 
             Log.d(this, "Finished InCallPresenter.CleanUp");
         }
@@ -1245,7 +1309,20 @@
      * @param rotation The device rotation.
      */
     public void onDeviceRotationChange(int rotation) {
+        Log.d(this, "onDeviceRotationChange: rotation=" + rotation);
         // First translate to rotation in degrees.
+        if (mCallList != null) {
+            mCallList.notifyCallsOfDeviceRotation(toRotationAngle(rotation));
+        } else {
+            Log.w(this, "onDeviceRotationChange: CallList is null.");
+        }
+    }
+
+    /**
+     * Converts rotation constants to rotation in degrees.
+     * @param rotation Rotation constants.
+     */
+    public static int toRotationAngle(int rotation) {
         int rotationAngle;
         switch (rotation) {
             case Surface.ROTATION_0:
@@ -1263,8 +1340,7 @@
             default:
                 rotationAngle = 0;
         }
-
-        mCallList.notifyCallsOfDeviceRotation(rotationAngle);
+        return rotationAngle;
     }
 
     /**
@@ -1286,6 +1362,7 @@
      */
     public void setInCallAllowsOrientationChange(boolean allowOrientationChange) {
         if (mInCallActivity == null) {
+            Log.e(this, "InCallActivity is null. Can't set requested orientation.");
             return;
         }
 
@@ -1296,6 +1373,21 @@
         }
     }
 
+    public void enableScreenTimeout(boolean enable) {
+        Log.v(this, "enableScreenTimeout: value=" + enable);
+        if (mInCallActivity == null) {
+            Log.e(this, "enableScreenTimeout: InCallActivity is null.");
+            return;
+        }
+
+        final Window window = mInCallActivity.getWindow();
+        if (enable) {
+            window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        } else {
+            window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+        }
+    }
+
     /**
      * Returns the space available beside the call card.
      *
diff --git a/InCallUI/src/com/android/incallui/InCallVideoCallListener.java b/InCallUI/src/com/android/incallui/InCallVideoCallListener.java
index 4ba2bd9..df9dfdc 100644
--- a/InCallUI/src/com/android/incallui/InCallVideoCallListener.java
+++ b/InCallUI/src/com/android/incallui/InCallVideoCallListener.java
@@ -18,6 +18,7 @@
 
 import android.telecom.CameraCapabilities;
 import android.telecom.Connection;
+import android.telecom.Connection.VideoProvider;
 import android.telecom.InCallService.VideoCall;
 import android.telecom.VideoProfile;
 
@@ -47,53 +48,50 @@
      */
     @Override
     public void onSessionModifyRequestReceived(VideoProfile videoProfile) {
-        int previousVideoState = mCall.getVideoState();
-        int newVideoState = videoProfile.getVideoState();
+        Log.d(this, " onSessionModifyRequestReceived videoProfile=" + videoProfile);
+        int previousVideoState = CallUtils.getUnPausedVideoState(mCall.getVideoState());
+        int newVideoState = CallUtils.getUnPausedVideoState(videoProfile.getVideoState());
 
-        boolean wasVideoCall = VideoProfile.VideoState.isBidirectional(previousVideoState);
-        boolean isVideoCall = VideoProfile.VideoState.isBidirectional(newVideoState);
-
-        boolean wasPaused = VideoProfile.VideoState.isPaused(previousVideoState);
-        boolean isPaused = VideoProfile.VideoState.isPaused(newVideoState);
+        boolean wasVideoCall = VideoProfile.VideoState.isVideo(previousVideoState);
+        boolean isVideoCall = VideoProfile.VideoState.isVideo(newVideoState);
 
         // Check for upgrades to video and downgrades to audio.
-        if (!wasVideoCall && isVideoCall) {
-            InCallVideoCallListenerNotifier.getInstance().upgradeToVideoRequest(mCall);
-        } else if (wasVideoCall && !isVideoCall) {
+        if (wasVideoCall && !isVideoCall) {
             InCallVideoCallListenerNotifier.getInstance().downgradeToAudio(mCall);
+        } else if (previousVideoState != newVideoState) {
+            InCallVideoCallListenerNotifier.getInstance().upgradeToVideoRequest(mCall,
+                newVideoState);
         }
-
-        boolean pause = !wasPaused && isPaused;
-        InCallVideoCallListenerNotifier.getInstance().peerPausedStateChanged(mCall, pause);
     }
 
     /**
      * Handles a session modification response.
      *
-     * @param status Status of the session modify request.  Valid values are
-     *               {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_SUCCESS},
-     *               {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_FAIL},
-     *               {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_INVALID}
+     * @param status Status of the session modify request. Valid values are
+     *            {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_SUCCESS},
+     *            {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_FAIL},
+     *            {@link Connection.VideoProvider#SESSION_MODIFY_REQUEST_INVALID}
      * @param requestedProfile
      * @param responseProfile The actual profile changes made by the peer device.
      */
     @Override
-    public void onSessionModifyResponseReceived(
-            int status, VideoProfile requestedProfile, VideoProfile responseProfile) {
-        boolean modifySucceeded =
-                requestedProfile.getVideoState() == responseProfile.getVideoState();
-        boolean isVideoCall =
-                VideoProfile.VideoState.isBidirectional(responseProfile.getVideoState());
-
-        if (modifySucceeded && isVideoCall) {
-            // Local Upgrade success
-            InCallVideoCallListenerNotifier.getInstance().upgradeToVideoSuccess(mCall);
-        } else if (!modifySucceeded || status != Connection.VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS) {
-            // Remote didn't accept invitation in bidirectional state or failure
-            InCallVideoCallListenerNotifier.getInstance().upgradeToVideoFail(mCall);
-        } else if (modifySucceeded && !isVideoCall) {
-            // Local Downgrade success (should always be successful)
-            InCallVideoCallListenerNotifier.getInstance().downgradeToAudio(mCall);
+    public void onSessionModifyResponseReceived(int status, VideoProfile requestedProfile,
+            VideoProfile responseProfile) {
+        Log.d(this, "onSessionModifyResponseReceived status=" + status + " requestedProfile="
+                + requestedProfile + " responseProfile=" + responseProfile);
+        if (status != VideoProvider.SESSION_MODIFY_REQUEST_SUCCESS) {
+            InCallVideoCallListenerNotifier.getInstance().upgradeToVideoFail(status, mCall);
+        } else if (requestedProfile != null && responseProfile != null) {
+            boolean modifySucceeded = requestedProfile.getVideoState() ==
+                    responseProfile.getVideoState();
+            boolean isVideoCall = VideoProfile.VideoState.isVideo(responseProfile.getVideoState());
+            if (modifySucceeded && isVideoCall) {
+                InCallVideoCallListenerNotifier.getInstance().upgradeToVideoSuccess(mCall);
+            } else if (!modifySucceeded) {
+                InCallVideoCallListenerNotifier.getInstance().upgradeToVideoFail(status, mCall);
+            }
+        } else {
+            Log.d(this, "onSessionModifyResponseReceived request and response Profiles are null");
         }
     }
 
@@ -104,6 +102,7 @@
      */
     @Override
     public void onCallSessionEvent(int event) {
+        InCallVideoCallListenerNotifier.getInstance().callSessionEvent(event);
     }
 
     /**
@@ -118,13 +117,25 @@
     }
 
     /**
+     * Handles a change to the video quality of the call.
+     *
+     * @param videoQuality The updated video call quality.
+     */
+    @Override
+    public void onVideoQualityChanged(int videoQuality) {
+        InCallVideoCallListenerNotifier.getInstance().videoQualityChanged(mCall, videoQuality);
+    }
+
+    /**
      * Handles a change to the call data usage.  No implementation as the in-call UI does not
      * display data usage.
      *
      * @param dataUsage The updated data usage.
      */
     @Override
-    public void onCallDataUsageChanged(int dataUsage) {
+    public void onCallDataUsageChanged(long dataUsage) {
+        Log.d(this, "onCallDataUsageChanged: dataUsage = " + dataUsage);
+        InCallVideoCallListenerNotifier.getInstance().callDataUsageChanged(dataUsage);
     }
 
     /**
diff --git a/InCallUI/src/com/android/incallui/InCallVideoCallListenerNotifier.java b/InCallUI/src/com/android/incallui/InCallVideoCallListenerNotifier.java
index 9f3f062..818ed03 100644
--- a/InCallUI/src/com/android/incallui/InCallVideoCallListenerNotifier.java
+++ b/InCallUI/src/com/android/incallui/InCallVideoCallListenerNotifier.java
@@ -123,12 +123,13 @@
 
     /**
      * Inform listeners of an upgrade to video request for a call.
-     *
      * @param call The call.
+     * @param videoState The video state we want to upgrade to.
      */
-    public void upgradeToVideoRequest(Call call) {
+    public void upgradeToVideoRequest(Call call, int videoState) {
+        Log.d(this, "upgradeToVideoRequest call = " + call + " new video state = " + videoState);
         for (SessionModificationListener listener : mSessionModificationListeners) {
-            listener.onUpgradeToVideoRequest(call);
+            listener.onUpgradeToVideoRequest(call, videoState);
         }
     }
 
@@ -148,9 +149,9 @@
      *
      * @param call The call.
      */
-    public void upgradeToVideoFail(Call call) {
+    public void upgradeToVideoFail(int status, Call call) {
         for (SessionModificationListener listener : mSessionModificationListeners) {
-            listener.onUpgradeToVideoFail(call);
+            listener.onUpgradeToVideoFail(status, call);
         }
     }
 
@@ -166,6 +167,17 @@
     }
 
     /**
+     * Inform listeners of a call session event.
+     *
+     * @param event The call session event.
+     */
+    public void callSessionEvent(int event) {
+        for (VideoEventListener listener : mVideoEventListeners) {
+            listener.onCallSessionEvent(event);
+        }
+    }
+
+    /**
      * Inform listeners of a downgrade to audio.
      *
      * @param call The call.
@@ -178,6 +190,18 @@
     }
 
     /**
+     * Inform listeners of any change in the video quality of the call
+     *
+     * @param call The call.
+     * @param videoQuality The updated video quality of the call.
+     */
+    public void videoQualityChanged(Call call, int videoQuality) {
+        for (VideoEventListener listener : mVideoEventListeners) {
+            listener.onVideoQualityChanged(call, videoQuality);
+        }
+    }
+
+    /**
      * Inform listeners of a change to peer dimensions.
      *
      * @param call The call.
@@ -204,6 +228,17 @@
     }
 
     /**
+     * Inform listeners of a change to call data usage.
+     *
+     * @param dataUsage data usage value
+     */
+    public void callDataUsageChanged(long dataUsage) {
+        for (VideoEventListener listener : mVideoEventListeners) {
+            listener.onCallDataUsageChange(dataUsage);
+        }
+    }
+
+    /**
      * Listener interface for any class that wants to be notified of upgrade to video and downgrade
      * to audio session modification requests.
      */
@@ -212,8 +247,9 @@
          * Called when a peer request is received to upgrade an audio-only call to a video call.
          *
          * @param call The call the request was received for.
+         * @param videoState The video state that the request wants to upgrade to.
          */
-        public void onUpgradeToVideoRequest(Call call);
+        public void onUpgradeToVideoRequest(Call call, int videoState);
 
         /**
          * Called when a request to a peer to upgrade an audio-only call to a video call is
@@ -230,7 +266,7 @@
          *
          * @param call The call the request was successful for.
          */
-        public void onUpgradeToVideoFail(Call call);
+        public void onUpgradeToVideoFail(int status, Call call);
 
         /**
          * Called when a call has been downgraded to audio-only.
@@ -242,7 +278,7 @@
 
     /**
      * Listener interface for any class that wants to be notified of video events, including pause
-     * and un-pause of peer video.
+     * and un-pause of peer video, video quality changes.
      */
     public interface VideoEventListener {
         /**
@@ -253,6 +289,29 @@
          *               otherwise.
          */
         public void onPeerPauseStateChanged(Call call, boolean paused);
+
+        /**
+         * Called when the video quality changes.
+         *
+         * @param call   The call whose video quality changes.
+         * @param videoCallQuality - values are QUALITY_HIGH, MEDIUM, LOW and UNKNOWN.
+         */
+        public void onVideoQualityChanged(Call call, int videoCallQuality);
+
+        /*
+         * Called when call data usage value is requested or when call data usage value is updated
+         * because of a call state change
+         *
+         * @param dataUsage call data usage value
+         */
+        public void onCallDataUsageChange(long dataUsage);
+
+        /**
+         * Called when call session event is raised.
+         *
+         * @param event The call session event.
+         */
+        public void onCallSessionEvent(int event);
     }
 
     /**
diff --git a/InCallUI/src/com/android/incallui/Log.java b/InCallUI/src/com/android/incallui/Log.java
index 07a0e61..5bc74b1 100644
--- a/InCallUI/src/com/android/incallui/Log.java
+++ b/InCallUI/src/com/android/incallui/Log.java
@@ -31,7 +31,7 @@
     // Generic tag for all In Call logging
     public static final String TAG = "InCall";
 
-    public static final boolean FORCE_DEBUG = false; /* STOPSHIP if true */
+    public static final boolean FORCE_DEBUG = true; /* STOPSHIP if true */
     public static final boolean DEBUG = FORCE_DEBUG ||
             android.util.Log.isLoggable(TAG, android.util.Log.DEBUG);
     public static final boolean VERBOSE = FORCE_DEBUG ||
diff --git a/InCallUI/src/com/android/incallui/VideoCallFragment.java b/InCallUI/src/com/android/incallui/VideoCallFragment.java
index 143ee24..233ff92 100644
--- a/InCallUI/src/com/android/incallui/VideoCallFragment.java
+++ b/InCallUI/src/com/android/incallui/VideoCallFragment.java
@@ -16,10 +16,14 @@
 
 package com.android.incallui;
 
+import android.content.Context;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.SurfaceTexture;
 import android.os.Bundle;
+import android.telecom.Connection;
+import android.telecom.VideoProfile;
 import android.view.Display;
 import android.view.LayoutInflater;
 import android.view.Surface;
@@ -28,6 +32,9 @@
 import android.view.ViewGroup;
 import android.view.ViewStub;
 import android.view.ViewTreeObserver;
+import android.widget.Toast;
+
+import com.google.common.base.Objects;
 
 /**
  * Fragment containing video calling surfaces.
@@ -52,11 +59,23 @@
      */
     public static final int SURFACE_PREVIEW = 2;
 
+    /**
+     * Used to indicate that the UI rotation is unknown.
+     */
+    public static final int ORIENTATION_UNKNOWN = -1;
+
+    /**
+     * Invalid resource id.
+     */
+    public static final int INVALID_RESOURCE_ID = -1;
+
+
     // Static storage used to retain the video surfaces across Activity restart.
     // TextureViews are not parcelable, so it is not possible to store them in the saved state.
     private static boolean sVideoSurfacesInUse = false;
     private static VideoCallSurface sPreviewSurface = null;
     private static VideoCallSurface sDisplaySurface = null;
+    private static Point sDisplaySize = null;
 
     /**
      * {@link ViewStub} holding the video call surfaces.  This is the parent for the
@@ -86,26 +105,20 @@
     private boolean mIsLandscape;
 
     /**
-     * The width of the surface.
-     */
-    private int mWidth = DIMENSIONS_NOT_SET;
-
-    /**
-     * The height of the surface.
-     */
-    private int mHeight = DIMENSIONS_NOT_SET;
-
-    /**
      * Inner-class representing a {@link TextureView} and its associated {@link SurfaceTexture} and
      * {@link Surface}.  Used to manage the lifecycle of these objects across device orientation
      * changes.
      */
-    private class VideoCallSurface implements TextureView.SurfaceTextureListener,
-            View.OnClickListener, View.OnAttachStateChangeListener {
+    private static class VideoCallSurface implements TextureView.SurfaceTextureListener,
+            View.OnClickListener {
         private int mSurfaceId;
+        private VideoCallPresenter mPresenter;
         private TextureView mTextureView;
         private SurfaceTexture mSavedSurfaceTexture;
         private Surface mSavedSurface;
+        private boolean mIsDoneWithSurface;
+        private int mWidth = DIMENSIONS_NOT_SET;
+        private int mHeight = DIMENSIONS_NOT_SET;
 
         /**
          * Creates an instance of a {@link VideoCallSurface}.
@@ -113,8 +126,9 @@
          * @param surfaceId The surface ID of the surface.
          * @param textureView The {@link TextureView} for the surface.
          */
-        public VideoCallSurface(int surfaceId, TextureView textureView) {
-            this(surfaceId, textureView, DIMENSIONS_NOT_SET, DIMENSIONS_NOT_SET);
+        public VideoCallSurface(VideoCallPresenter presenter, int surfaceId,
+                TextureView textureView) {
+            this(presenter, surfaceId, textureView, DIMENSIONS_NOT_SET, DIMENSIONS_NOT_SET);
         }
 
         /**
@@ -125,7 +139,11 @@
          * @param width The width of the surface.
          * @param height The height of the surface.
          */
-        public VideoCallSurface(int surfaceId, TextureView textureView, int width, int height) {
+        public VideoCallSurface(VideoCallPresenter presenter,int surfaceId, TextureView textureView,
+                int width, int height) {
+            Log.d(this, "VideoCallSurface: surfaceId=" + surfaceId +
+                    " width=" + width + " height=" + height);
+            mPresenter = presenter;
             mWidth = width;
             mHeight = height;
             mSurfaceId = surfaceId;
@@ -151,7 +169,24 @@
             mTextureView = view;
             mTextureView.setSurfaceTextureListener(this);
             mTextureView.setOnClickListener(this);
-            mTextureView.addOnAttachStateChangeListener(this);
+
+            final boolean areSameSurfaces =
+                    Objects.equal(mSavedSurfaceTexture, mTextureView.getSurfaceTexture());
+            Log.d(this, "recreateView: SavedSurfaceTexture=" + mSavedSurfaceTexture
+                    + " areSameSurfaces=" + areSameSurfaces);
+            if (mSavedSurfaceTexture != null && !areSameSurfaces) {
+                mTextureView.setSurfaceTexture(mSavedSurfaceTexture);
+                if (createSurface(mWidth, mHeight)) {
+                    onSurfaceCreated();
+                }
+            }
+            mIsDoneWithSurface = false;
+        }
+
+        public void resetPresenter(VideoCallPresenter presenter) {
+            Log.d(this, "resetPresenter: CurrentPresenter=" + mPresenter + " NewPresenter="
+                    + presenter);
+            mPresenter = presenter;
         }
 
         /**
@@ -172,17 +207,31 @@
             // Where there is no saved {@link SurfaceTexture} available, use the newly created one.
             // If a saved {@link SurfaceTexture} is available, we are re-creating after an
             // orientation change.
+            Log.d(this, " onSurfaceTextureAvailable mSurfaceId=" + mSurfaceId + " surfaceTexture="
+                    + surfaceTexture + " width=" + width
+                    + " height=" + height + " mSavedSurfaceTexture=" + mSavedSurfaceTexture);
+            Log.d(this, " onSurfaceTextureAvailable VideoCallPresenter=" + mPresenter);
             if (mSavedSurfaceTexture == null) {
                 mSavedSurfaceTexture = surfaceTexture;
                 surfaceCreated = createSurface(width, height);
             } else {
                 // A saved SurfaceTexture was found.
+                Log.d(this, " onSurfaceTextureAvailable: Replacing with cached surface...");
+                mTextureView.setSurfaceTexture(mSavedSurfaceTexture);
                 surfaceCreated = true;
             }
 
             // Inform presenter that the surface is available.
             if (surfaceCreated) {
-                getPresenter().onSurfaceCreated(mSurfaceId);
+                onSurfaceCreated();
+            }
+        }
+
+        private void onSurfaceCreated() {
+            if (mPresenter != null) {
+                mPresenter.onSurfaceCreated(mSurfaceId);
+            } else {
+                Log.e(this, "onSurfaceTextureAvailable: Presenter is null");
             }
         }
 
@@ -210,17 +259,30 @@
             /**
              * Destroying the surface texture; inform the presenter so it can null the surfaces.
              */
-            if (mSavedSurfaceTexture == null) {
-                getPresenter().onSurfaceDestroyed(mSurfaceId);
+            Log.d(this, " onSurfaceTextureDestroyed mSurfaceId=" + mSurfaceId + " surfaceTexture="
+                    + surfaceTexture + " SavedSurfaceTexture=" + mSavedSurfaceTexture
+                    + " SavedSurface=" + mSavedSurface);
+            Log.d(this, " onSurfaceTextureDestroyed VideoCallPresenter=" + mPresenter);
+
+            // Notify presenter if it is not null.
+            onSurfaceDestroyed();
+
+            if (mIsDoneWithSurface) {
+                onSurfaceReleased();
                 if (mSavedSurface != null) {
                     mSavedSurface.release();
                     mSavedSurface = null;
                 }
             }
+            return mIsDoneWithSurface;
+        }
 
-            // The saved SurfaceTexture will be null if we're shutting down, so we want to
-            // return "true" in that case (indicating that TextureView can release the ST).
-            return (mSavedSurfaceTexture == null);
+        private void onSurfaceDestroyed() {
+            if (mPresenter != null) {
+                mPresenter.onSurfaceDestroyed(mSurfaceId);
+            } else {
+                Log.e(this, "onSurfaceTextureDestroyed: Presenter is null.");
+            }
         }
 
         /**
@@ -259,7 +321,15 @@
          * change in video state.  Releases and clears out the saved surface and surface textures.
          */
         public void setDoneWithSurface() {
+            Log.d(this, "setDoneWithSurface: SavedSurface=" + mSavedSurface
+                    + " SavedSurfaceTexture=" + mSavedSurfaceTexture);
+            mIsDoneWithSurface = true;
+            if (mTextureView != null && mTextureView.isAvailable()) {
+                return;
+            }
+
             if (mSavedSurface != null) {
+                onSurfaceReleased();
                 mSavedSurface.release();
                 mSavedSurface = null;
             }
@@ -269,6 +339,14 @@
             }
         }
 
+        private void onSurfaceReleased() {
+            if (mPresenter != null) {
+                mPresenter.onSurfaceReleased(mSurfaceId);
+            } else {
+                Log.d(this, "setDoneWithSurface: Presenter is null.");
+            }
+        }
+
         /**
          * Retrieves the saved surface instance.
          *
@@ -285,10 +363,12 @@
          * @param height The height of the surface, in pixels.
          */
         public void setSurfaceDimensions(int width, int height) {
+            Log.d(this, "setSurfaceDimensions, width=" + width + " height=" + height);
             mWidth = width;
             mHeight = height;
 
             if (mSavedSurfaceTexture != null) {
+                Log.d(this, "setSurfaceDimensions, mSavedSurfaceTexture is NOT equal to null.");
                 createSurface(width, height);
             }
         }
@@ -299,9 +379,10 @@
          * @param height The height of the surface to create.
          */
         private boolean createSurface(int width, int height) {
+            Log.d(this, "createSurface mSavedSurfaceTexture=" + mSavedSurfaceTexture
+                    + " mSurfaceId =" + mSurfaceId + " mWidth " + width + " mHeight=" + height);
             if (width != DIMENSIONS_NOT_SET && height != DIMENSIONS_NOT_SET
                     && mSavedSurfaceTexture != null) {
-
                 mSavedSurfaceTexture.setDefaultBufferSize(width, height);
                 mSavedSurface = new Surface(mSavedSurfaceTexture);
                 return true;
@@ -317,7 +398,11 @@
          */
         @Override
         public void onClick(View view) {
-            getPresenter().onSurfaceClick(mSurfaceId);
+            if (mPresenter != null) {
+                mPresenter.onSurfaceClick(mSurfaceId);
+            } else {
+                Log.e(this, "onClick: Presenter is null.");
+            }
         }
     };
 
@@ -339,6 +424,7 @@
         mIsLandscape = getResources().getConfiguration().orientation
                 == Configuration.ORIENTATION_LANDSCAPE;
 
+        Log.d(this, "onActivityCreated: IsLandscape=" + mIsLandscape);
         getPresenter().init(getActivity());
     }
 
@@ -375,9 +461,14 @@
         // In a right-to-left locale, the space for the video view is to the left of the call card
         // so we need to translate it in the -X direction.
         final boolean isLayoutRtl = InCallPresenter.isRtl();
+
+        ViewGroup.LayoutParams params = displayVideo.getLayoutParams();
         float spaceBesideCallCard = InCallPresenter.getInstance().getSpaceBesideCallCard();
+        Log.d(this, "centerDisplayView: IsLandscape= " + mIsLandscape + " Layout width: " +
+                params.width + " height: " + params.height + " spaceBesideCallCard: "
+                + spaceBesideCallCard);
         if (mIsLandscape) {
-            float videoViewTranslation = displayVideo.getWidth() / 2
+            float videoViewTranslation = params.width / 2
                     - spaceBesideCallCard / 2;
             if (isLayoutRtl) {
                 displayVideo.setTranslationX(-videoViewTranslation);
@@ -385,7 +476,7 @@
                 displayVideo.setTranslationX(videoViewTranslation);
             }
         } else {
-            float videoViewTranslation = displayVideo.getHeight() / 2
+            float videoViewTranslation = params.height / 2
                     - spaceBesideCallCard / 2;
             displayVideo.setTranslationY(videoViewTranslation);
         }
@@ -400,6 +491,7 @@
     @Override
     public void onViewCreated(View view, Bundle savedInstanceState) {
         super.onViewCreated(view, savedInstanceState);
+        Log.d(this, "onViewCreated: VideoSurfacesInUse=" + sVideoSurfacesInUse);
 
         mVideoViewsStub = (ViewStub) view.findViewById(R.id.videoCallViewsStub);
 
@@ -411,13 +503,34 @@
         }
     }
 
+    @Override
+    public void onStop() {
+        super.onStop();
+        Log.d(this, "onStop:");
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        Log.d(this, "onPause:");
+    }
+
+    @Override
+    public void onDestroyView() {
+        super.onDestroyView();
+        Log.d(this, "onDestroyView:");
+    }
+
     /**
      * Creates the presenter for the {@link VideoCallFragment}.
      * @return The presenter instance.
      */
     @Override
     public VideoCallPresenter createPresenter() {
-        return new VideoCallPresenter();
+        Log.d(this, "createPresenter");
+        VideoCallPresenter presenter = new VideoCallPresenter();
+        onPresenterChanged(presenter);
+        return presenter;
     }
 
     /**
@@ -429,33 +542,144 @@
     }
 
     /**
-     * Toggles visibility of the video UI.
+     * Inflate video surfaces.
      *
      * @param show {@code True} if the video surfaces should be shown.
      */
-    @Override
-    public void showVideoUi(boolean show) {
+    private void inflateVideoUi(boolean show) {
         int visibility = show ? View.VISIBLE : View.GONE;
         getView().setVisibility(visibility);
 
         if (show) {
             inflateVideoCallViews();
-        } else {
-            cleanupSurfaces();
         }
 
-        if (mVideoViews != null ) {
+        if (mVideoViews != null) {
             mVideoViews.setVisibility(visibility);
         }
     }
 
     /**
+     * Show or hide preview and incoming video views
+     */
+    public void showVideoViews(boolean showPreview, boolean showIncoming) {
+        inflateVideoUi(true);
+
+        View incomingVideoView = mVideoViews.findViewById(R.id.incomingVideo);
+        View previewVideoView = mVideoViews.findViewById(R.id.previewVideo);
+
+        if (incomingVideoView != null) {
+            incomingVideoView.setVisibility(showIncoming ? View.VISIBLE : View.INVISIBLE);
+        }
+        if (previewVideoView != null) {
+            previewVideoView.setVisibility(showPreview ? View.VISIBLE : View.INVISIBLE);
+        }
+    }
+
+    /**
+     * Hide all video views.
+     */
+    public void hideVideoUi() {
+        inflateVideoUi(false);
+    }
+
+    /**
+     * Displays a message on the UI that the video call quality has changed.
+     *
+     */
+    @Override
+    public void showVideoQualityChanged(int videoQuality) {
+        Log.d(this, "showVideoQualityChanged. Video quality changed to " + videoQuality);
+
+        final Context context = getActivity();
+        if (context == null) {
+            Log.e(this, "showVideoQualityChanged - Activity is null. Return");
+            return;
+        }
+
+        final Resources resources = context.getResources();
+
+        int videoQualityResourceId = R.string.video_quality_unknown;
+        switch (videoQuality) {
+            case VideoProfile.QUALITY_HIGH:
+                videoQualityResourceId = R.string.video_quality_high;
+                break;
+            case VideoProfile.QUALITY_MEDIUM:
+                videoQualityResourceId = R.string.video_quality_medium;
+                break;
+            case VideoProfile.QUALITY_LOW:
+                videoQualityResourceId = R.string.video_quality_low;
+                break;
+            default:
+                break;
+        }
+
+        String videoQualityChangedText = resources.getString(R.string.video_quality_changed) +
+            resources.getString(videoQualityResourceId);
+
+        Toast.makeText(context, videoQualityChangedText, Toast.LENGTH_SHORT).show();
+    }
+
+    /**
+     * Displays a message on the UI that the call substate has changed.
+     *
+     */
+    @Override
+    public void showCallSubstateChanged(int callSubstate) {
+        Log.d(this, "showCallSubstateChanged - call substate changed to "  + callSubstate);
+
+        final Context context = getActivity();
+        if (context == null) {
+            Log.e(this, "showCallSubstateChanged - Activity is null. Return");
+            return;
+        }
+
+        final Resources resources = context.getResources();
+
+        String callSubstateChangedText = "";
+
+        if (isEnabled(Connection.SUBSTATE_AUDIO_CONNECTED_SUSPENDED, callSubstate)) {
+            callSubstateChangedText +=
+                resources.getString(R.string.call_substate_connected_suspended_audio);
+        }
+
+        if (isEnabled(Connection.SUBSTATE_VIDEO_CONNECTED_SUSPENDED, callSubstate)) {
+            callSubstateChangedText +=
+                resources.getString(R.string.call_substate_connected_suspended_video);
+        }
+
+        if (isEnabled(Connection.SUBSTATE_AVP_RETRY, callSubstate)) {
+            callSubstateChangedText +=
+                resources.getString(R.string.call_substate_avp_retry);
+        }
+
+        if (isNotEnabled(Connection.SUBSTATE_ALL, callSubstate)) {
+            callSubstateChangedText = resources.getString(R.string.call_substate_call_resumed);
+        }
+
+        if (!callSubstateChangedText.isEmpty()) {
+            String callSubstateLabelText = resources.getString(R.string.call_substate_label);
+            Toast.makeText(context, callSubstateLabelText + callSubstateChangedText,
+                Toast.LENGTH_SHORT).show();
+        }
+    }
+
+    boolean isEnabled(int mask, int callSubstate) {
+        return (mask & callSubstate) == mask;
+    }
+
+    boolean isNotEnabled(int mask, int callSubstate) {
+        return (mask & callSubstate) == 0;
+    }
+
+    /**
      * Cleans up the video telephony surfaces.  Used when the presenter indicates a change to an
      * audio-only state.  Since the surfaces are static, it is important to ensure they are cleaned
      * up promptly.
      */
     @Override
     public void cleanupSurfaces() {
+        Log.d(this, "cleanupSurfaces");
         if (sDisplaySurface != null) {
             sDisplaySurface.setDoneWithSurface();
             sDisplaySurface = null;
@@ -467,8 +691,19 @@
         sVideoSurfacesInUse = false;
     }
 
+    private void onPresenterChanged(VideoCallPresenter presenter) {
+        Log.d(this, "onPresenterChanged: Presenter=" + presenter);
+        if (sDisplaySurface != null) {
+            sDisplaySurface.resetPresenter(presenter);;
+        }
+        if (sPreviewSurface != null) {
+            sPreviewSurface.resetPresenter(presenter);
+        }
+    }
+
     @Override
     public boolean isActivityRestart() {
+        Log.d(this, "isActivityRestart " + mIsActivityRestart);
         return mIsActivityRestart;
     }
 
@@ -477,7 +712,9 @@
      */
     @Override
     public boolean isDisplayVideoSurfaceCreated() {
-        return sDisplaySurface != null && sDisplaySurface.getSurface() != null;
+        boolean ret = sDisplaySurface != null && sDisplaySurface.getSurface() != null;
+        Log.d(this, " isDisplayVideoSurfaceCreated returns " + ret);
+        return ret;
     }
 
     /**
@@ -485,7 +722,9 @@
      */
     @Override
     public boolean isPreviewVideoSurfaceCreated() {
-        return sPreviewSurface != null && sPreviewSurface.getSurface() != null;
+        boolean ret = sPreviewSurface != null && sPreviewSurface.getSurface() != null;
+        Log.d(this, " isPreviewVideoSurfaceCreated returns " + ret);
+        return ret;
     }
 
     /**
@@ -515,6 +754,7 @@
      */
     @Override
     public void setPreviewSize(int width, int height) {
+        Log.d(this, "setPreviewSize: width=" + width + " height=" + height);
         if (sPreviewSurface != null) {
             TextureView preview = sPreviewSurface.getTextureView();
 
@@ -527,15 +767,123 @@
             params.height = height;
             preview.setLayoutParams(params);
 
+            int rotation = InCallPresenter.toRotationAngle(getCurrentRotation());
+            int rotationAngle = 360 - rotation;
+            preview.setRotation(rotationAngle);
+            Log.d(this, "setPreviewSize: rotation=" + rotation +
+                    " rotationAngle=" + rotationAngle);
+
+        }
+    }
+
+    @Override
+    public void setPreviewSurfaceSize(int width, int height) {
+        final boolean isPreviewSurfaceAvailable = sPreviewSurface != null;
+        Log.d(this, "setPreviewSurfaceSize: width=" + width + " height=" + height +
+                " isPreviewSurfaceAvailable=" + isPreviewSurfaceAvailable);
+        if (isPreviewSurfaceAvailable) {
             sPreviewSurface.setSurfaceDimensions(width, height);
         }
     }
 
     /**
+     * returns UI's current orientation.
+     */
+    @Override
+    public int getCurrentRotation() {
+        try {
+            return getActivity().getWindowManager().getDefaultDisplay().getRotation();
+        } catch (Exception e) {
+            Log.e(this, "getCurrentRotation: Retrieving current rotation failed. Ex=" + e);
+        }
+        return ORIENTATION_UNKNOWN;
+    }
+
+    /**
+     * Changes the dimensions of the display video surface. Called when the dimensions change due to
+     * a peer resolution update
+     *
+     * @param width The new width.
+     * @param height The new height.
+     */
+    @Override
+    public void setDisplayVideoSize(int width, int height) {
+        Log.d(this, "setDisplayVideoSize: width=" + width + " height=" + height);
+        if (sDisplaySurface != null) {
+            TextureView displayVideo = sDisplaySurface.getTextureView();
+            if (displayVideo == null) {
+                Log.e(this, "Display Video texture view is null. Bail out");
+                return;
+            }
+            sDisplaySize = new Point(width, height);
+            setSurfaceSizeAndTranslation(displayVideo, sDisplaySize);
+        } else {
+            Log.e(this, "Display Video Surface is null. Bail out");
+        }
+    }
+
+    /**
+     * Sets the call's data usage value
+     *
+     * @param context the current context
+     * @param dataUsage the data usage value
+     */
+    @Override
+    public void setCallDataUsage(Context context, long dataUsage) {
+        Log.d(this, "setDataUsage: dataUsage = " + dataUsage);
+        Toast.makeText(context, "dataUsage=" + dataUsage, Toast.LENGTH_LONG).show();
+    }
+
+    private int fromCallSessionEvent(int event) {
+        switch (event) {
+            case Connection.VideoProvider.SESSION_EVENT_RX_PAUSE:
+                return R.string.player_stopped;
+            case Connection.VideoProvider.SESSION_EVENT_RX_RESUME:
+                return R.string.player_started;
+            case Connection.VideoProvider.SESSION_EVENT_CAMERA_FAILURE:
+                return R.string.camera_not_ready;
+            case Connection.VideoProvider.SESSION_EVENT_CAMERA_READY:
+                return R.string.camera_ready;
+            default:
+                return R.string.unknown_call_session_event;
+        }
+    }
+
+    /**
+     * Sets the call's data usage value
+     *
+     * @param context the current context
+     * @param event the call session event
+     */
+    @Override
+    public void displayCallSessionEvent(int event) {
+        Log.d(this, "displayCallSessionEvent: event = " + event);
+        Context context = getActivity();
+        String msg = context.getResources().getString(fromCallSessionEvent(event));
+        Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
+    }
+
+    /**
+     * Determines the size of the device screen.
+     *
+     * @return {@link Point} specifying the width and height of the screen.
+     */
+    @Override
+    public Point getScreenSize() {
+        // Get current screen size.
+        Display display = getActivity().getWindowManager().getDefaultDisplay();
+        Point size = new Point();
+        display.getSize(size);
+
+        return size;
+    }
+
+    /**
      * Inflates the {@link ViewStub} containing the incoming and outgoing surfaces, if necessary,
      * and creates {@link VideoCallSurface} instances to track the surfaces.
      */
     private void inflateVideoCallViews() {
+        Log.d(this, "inflateVideoCallViews");
         if (mVideoViews == null ) {
             mVideoViews = mVideoViewsStub.inflate();
         }
@@ -543,16 +891,20 @@
         if (mVideoViews != null) {
             TextureView displaySurface = (TextureView) mVideoViews.findViewById(R.id.incomingVideo);
 
-            Point screenSize = getScreenSize();
+            Log.d(this, "inflateVideoCallViews: sVideoSurfacesInUse=" + sVideoSurfacesInUse);
+            //If peer adjusted screen size is not available, set screen size to default display size
+            Point screenSize = sDisplaySize == null ? getScreenSize() : sDisplaySize;
             setSurfaceSizeAndTranslation(displaySurface, screenSize);
 
             if (!sVideoSurfacesInUse) {
                 // Where the video surfaces are not already in use (first time creating them),
                 // setup new VideoCallSurface instances to track them.
-                sDisplaySurface = new VideoCallSurface(SURFACE_DISPLAY,
+                Log.d(this, " inflateVideoCallViews screenSize" + screenSize);
+
+                sDisplaySurface = new VideoCallSurface(getPresenter(), SURFACE_DISPLAY,
                         (TextureView) mVideoViews.findViewById(R.id.incomingVideo), screenSize.x,
                         screenSize.y);
-                sPreviewSurface = new VideoCallSurface(SURFACE_PREVIEW,
+                sPreviewSurface = new VideoCallSurface(getPresenter(), SURFACE_PREVIEW,
                         (TextureView) mVideoViews.findViewById(R.id.previewVideo));
                 sVideoSurfacesInUse = true;
             } else {
@@ -600,28 +952,15 @@
         params.width = size.x;
         params.height = size.y;
         textureView.setLayoutParams(params);
+        Log.d(this, "setSurfaceSizeAndTranslation: Size=" + size + "IsLayoutComplete=" +
+                mIsLayoutComplete + "IsLandscape=" + mIsLandscape);
 
         // It is only possible to center the display view if layout of the views has completed.
         // It is only after layout is complete that the dimensions of the Call Card has been
         // established, which is a prerequisite to centering the view.
         // Incoming video calls will center the view
-        if (mIsLayoutComplete && ((mIsLandscape && textureView.getTranslationX() == 0) || (
-                !mIsLandscape && textureView.getTranslationY() == 0))) {
+        if (mIsLayoutComplete) {
             centerDisplayView(textureView);
         }
     }
-
-    /**
-     * Determines the size of the device screen.
-     *
-     * @return {@link Point} specifying the width and height of the screen.
-     */
-    private Point getScreenSize() {
-        // Get current screen size.
-        Display display = getActivity().getWindowManager().getDefaultDisplay();
-        Point size = new Point();
-        display.getSize(size);
-
-        return size;
-    }
 }
diff --git a/InCallUI/src/com/android/incallui/VideoCallPresenter.java b/InCallUI/src/com/android/incallui/VideoCallPresenter.java
index d63c6e8..e4a5db9 100644
--- a/InCallUI/src/com/android/incallui/VideoCallPresenter.java
+++ b/InCallUI/src/com/android/incallui/VideoCallPresenter.java
@@ -18,10 +18,14 @@
 
 import android.content.Context;
 import android.content.res.Configuration;
+import android.graphics.Point;
 import android.os.Handler;
 import android.telecom.AudioState;
 import android.telecom.CameraCapabilities;
+import android.telecom.Connection;
+import android.telecom.Connection.VideoProvider;
 import android.telecom.InCallService.VideoCall;
+import android.telecom.VideoProfile;
 import android.view.Surface;
 
 import com.android.contacts.common.CallUtil;
@@ -31,10 +35,14 @@
 import com.android.incallui.InCallPresenter.IncomingCallListener;
 import com.android.incallui.InCallVideoCallListenerNotifier.SurfaceChangeListener;
 import com.android.incallui.InCallVideoCallListenerNotifier.VideoEventListener;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyProperties;
 import com.google.common.base.Preconditions;
 
 import java.util.Objects;
 
+import android.os.SystemProperties;
+
 /**
  * Logic related to the {@link VideoCallFragment} and for managing changes to the video calling
  * surfaces based on other user interface events and incoming events from the
@@ -61,6 +69,7 @@
         IncomingCallListener, InCallOrientationListener, InCallStateListener,
         InCallDetailsListener, SurfaceChangeListener, VideoEventListener,
         InCallVideoCallListenerNotifier.SessionModificationListener {
+    public static final String TAG = "VideoCallPresenter";
 
     private static final String TAG = VideoCallPresenter.class.getSimpleName();
 
@@ -126,7 +135,12 @@
     /**
      * Determines if the current UI state represents a video call.
      */
-    private boolean mIsVideoCall;
+    private int mCurrentVideoState;
+
+    /**
+     * Call's current state
+     */
+    private int mCurrentCallState = Call.State.INVALID;
 
     /**
      * Determines the device orientation (portrait/lanscape).
@@ -146,7 +160,15 @@
     /**
      * Saves the audio mode which was selected prior to going into a video call.
      */
-    private int mPreVideoAudioMode = AudioModeProvider.AUDIO_MODE_INVALID;
+    private static int sPrevVideoAudioMode = AudioModeProvider.AUDIO_MODE_INVALID;
+
+    private static boolean mIsVideoMode = false;
+
+    /**
+     * Stores the current call substate.
+     */
+    private int mCurrentCallSubstate;
+
 
     /** Handler which resets request state to NO_REQUEST after an interval. */
     private Handler mSessionModificationResetHandler;
@@ -172,9 +194,11 @@
     @Override
     public void onUiReady(VideoCallUi ui) {
         super.onUiReady(ui);
+        Log.d(this, "onUiReady:");
 
         // Register for call state changes last
         InCallPresenter.getInstance().addListener(this);
+        InCallPresenter.getInstance().addDetailsListener(this);
         InCallPresenter.getInstance().addIncomingCallListener(this);
         InCallPresenter.getInstance().addOrientationListener(this);
         // To get updates of video call details changes
@@ -184,7 +208,8 @@
         InCallVideoCallListenerNotifier.getInstance().addSurfaceChangeListener(this);
         InCallVideoCallListenerNotifier.getInstance().addVideoEventListener(this);
         InCallVideoCallListenerNotifier.getInstance().addSessionModificationListener(this);
-        mIsVideoCall = false;
+        mCurrentVideoState = VideoProfile.VideoState.AUDIO_ONLY;
+        mCurrentCallState = Call.State.INVALID;
     }
 
     /**
@@ -195,44 +220,44 @@
     @Override
     public void onUiUnready(VideoCallUi ui) {
         super.onUiUnready(ui);
+        Log.d(this, "onUiUnready:");
 
         InCallPresenter.getInstance().removeListener(this);
+        InCallPresenter.getInstance().removeDetailsListener(this);
         InCallPresenter.getInstance().removeIncomingCallListener(this);
         InCallPresenter.getInstance().removeOrientationListener(this);
+
         InCallVideoCallListenerNotifier.getInstance().removeSurfaceChangeListener(this);
         InCallVideoCallListenerNotifier.getInstance().removeVideoEventListener(this);
         InCallVideoCallListenerNotifier.getInstance().removeSessionModificationListener(this);
     }
 
     /**
-     * @return The {@link VideoCall}.
-     */
-    private VideoCall getVideoCall() {
-        return mVideoCall;
-    }
-
-    /**
      * Handles the creation of a surface in the {@link VideoCallFragment}.
      *
      * @param surface The surface which was created.
      */
     public void onSurfaceCreated(int surface) {
-        if (DEBUG) {
-            Log.i(TAG, "onSurfaceCreated: " + surface);
-        }
-        final VideoCallUi ui = getUi();
+        Log.d(this, "onSurfaceCreated surface=" + surface + " mVideoCall=" + mVideoCall);
+        Log.d(this, "onSurfaceCreated PreviewSurfaceState=" + mPreviewSurfaceState);
+        Log.d(this, "onSurfaceCreated presenter=" + this);
 
+        final VideoCallUi ui = getUi();
         if (ui == null || mVideoCall == null) {
+            Log.w(this, "onSurfaceCreated: Error bad state VideoCallUi=" + ui + " mVideoCall="
+                    + mVideoCall);
             return;
         }
 
         // If the preview surface has just been created and we have already received camera
         // capabilities, but not yet set the surface, we will set the surface now.
-        if (surface == VideoCallFragment.SURFACE_PREVIEW &&
-                mPreviewSurfaceState == PreviewSurfaceState.CAPABILITIES_RECEIVED) {
-
-            mPreviewSurfaceState = PreviewSurfaceState.SURFACE_SET;
-            mVideoCall.setPreviewSurface(ui.getPreviewVideoSurface());
+        if (surface == VideoCallFragment.SURFACE_PREVIEW ) {
+            if (mPreviewSurfaceState == PreviewSurfaceState.CAPABILITIES_RECEIVED) {
+                mPreviewSurfaceState = PreviewSurfaceState.SURFACE_SET;
+                mVideoCall.setPreviewSurface(ui.getPreviewVideoSurface());
+            } else if (mPreviewSurfaceState == PreviewSurfaceState.NONE && isCameraRequired()){
+                enableCamera(mVideoCall, true);
+            }
         } else if (surface == VideoCallFragment.SURFACE_DISPLAY) {
             mVideoCall.setDisplaySurface(ui.getDisplayVideoSurface());
         }
@@ -252,12 +277,15 @@
 
     /**
      * Handles the destruction of a surface in the {@link VideoCallFragment}.
+     * Note: The surface is being released, that is, it is no longer valid.
      *
      * @param surface The surface which was destroyed.
      */
-    public void onSurfaceDestroyed(int surface) {
-        final VideoCallUi ui = getUi();
-        if (ui == null || mVideoCall == null) {
+    public void onSurfaceReleased(int surface) {
+        Log.d(this, "onSurfaceReleased: mSurfaceId=" + surface);
+        if ( mVideoCall == null) {
+            Log.w(this, "onSurfaceReleased: VideoCall is null. mSurfaceId=" +
+                    surface);
             return;
         }
 
@@ -265,12 +293,44 @@
             mVideoCall.setDisplaySurface(null);
         } else if (surface == VideoCallFragment.SURFACE_PREVIEW) {
             mVideoCall.setPreviewSurface(null);
-            // Also disable camera as preview is closed
-            mVideoCall.setCamera(null);
+            enableCamera(mVideoCall, false);
         }
     }
 
     /**
+     * Called by {@link VideoCallFragment} when the surface is detached from UI (TextureView).
+     * Note: The surface will be cached by {@link VideoCallFragment}, so we don't immediately
+     * null out incoming video surface.
+     * @see VideoCallPresenter#onSurfaceReleased(int)
+     *
+     * @param surface The surface which was detached.
+     */
+    public void onSurfaceDestroyed(int surface) {
+        Log.d(this, "onSurfaceDestroyed: mSurfaceId=" + surface);
+        if (mVideoCall == null) {
+            return;
+        }
+
+        final boolean isChangingConfigurations =
+                InCallPresenter.getInstance().isChangingConfigurations();
+        Log.d(this, "onSurfaceDestroyed: isChangingConfigurations=" + isChangingConfigurations);
+
+        if (surface == VideoCallFragment.SURFACE_PREVIEW) {
+            if (!isChangingConfigurations) {
+                enableCamera(mVideoCall, false);
+            } else {
+                Log.w(this, "onSurfaceDestroyed: Activity is being destroyed due "
+                        + "to configuration changes. Not closing the camera.");
+            }
+        }
+    }
+
+    private void toggleFullScreen() {
+        mIsFullScreen = !mIsFullScreen;
+        InCallPresenter.getInstance().setFullScreenVideoState(mIsFullScreen);
+    }
+
+    /**
      * Handles clicks on the video surfaces by toggling full screen state.
      * Informs the {@link InCallPresenter} of the change so that it can inform the
      * {@link CallCardPresenter} of the change.
@@ -278,8 +338,7 @@
      * @param surfaceId The video surface receiving the click.
      */
     public void onSurfaceClick(int surfaceId) {
-        mIsFullScreen = !mIsFullScreen;
-        InCallPresenter.getInstance().setFullScreenVideoState(mIsFullScreen);
+        toggleFullScreen();
     }
 
 
@@ -305,41 +364,152 @@
     @Override
     public void onStateChange(InCallPresenter.InCallState oldState,
             InCallPresenter.InCallState newState, CallList callList) {
-        // Bail if video calling is disabled for the device.
-        if (!CallUtil.isVideoEnabled(mContext)) {
-            return;
-        }
+        Log.d(this, "onStateChange oldState" + oldState + " newState=" + newState +
+                " isVideoMode=" + isVideoMode());
 
         if (newState == InCallPresenter.InCallState.NO_CALLS) {
-            exitVideoMode();
+            updateAudioMode(false);
+
+            if (isVideoMode()) {
+                exitVideoMode();
+            }
+
+            cleanupSurfaces();
         }
 
         // Determine the primary active call).
         Call primary = null;
         if (newState == InCallPresenter.InCallState.INCOMING) {
-            primary = callList.getIncomingCall();
+            // We don't want to replace active video call (primary call)
+            // with a waiting call, since user may choose to ignore/decline the waiting call and
+            // this should have no impact on current active video call, that is, we should not
+            // change the camera or UI unless the waiting VT call becomes active.
+            primary = callList.getActiveCall();
+            if (!CallUtils.isActiveVideoCall(primary)) {
+                primary = callList.getIncomingCall();
+            }
         } else if (newState == InCallPresenter.InCallState.OUTGOING) {
             primary = callList.getOutgoingCall();
+        } else if (newState == InCallPresenter.InCallState.PENDING_OUTGOING) {
+            primary = callList.getPendingOutgoingCall();
         } else if (newState == InCallPresenter.InCallState.INCALL) {
             primary = callList.getActiveCall();
         }
 
         final boolean primaryChanged = !Objects.equals(mPrimaryCall, primary);
+        Log.d(this, "onStateChange primaryChanged=" + primaryChanged);
+        Log.d(this, "onStateChange primary= " + primary);
+        Log.d(this, "onStateChange mPrimaryCall = " + mPrimaryCall);
         if (primaryChanged) {
-            mPrimaryCall = primary;
+            onPrimaryCallChanged(primary);
+        } else if(mPrimaryCall!=null) {
+            updateVideoCall(primary);
+        }
+        updateCallCache(primary);
+    }
 
-            if (primary != null) {
-                checkForVideoCallChange();
-                mIsVideoCall = mPrimaryCall.isVideoCall(mContext);
-                if (mIsVideoCall) {
-                    enterVideoMode();
-                } else {
-                    exitVideoMode();
-                }
-            } else if (primary == null) {
-                // If no primary call, ensure we exit video state and clean up the video surfaces.
-                exitVideoMode();
+    private void checkForVideoStateChange(Call call) {
+        final boolean isVideoCall = CallUtils.isVideoCall(call);
+        final boolean hasVideoStateChanged = mCurrentVideoState != call.getVideoState();
+
+        Log.d(this, "checkForVideoStateChange: isVideoCall= " + isVideoCall
+                + " hasVideoStateChanged=" +
+                hasVideoStateChanged + " isVideoMode=" + isVideoMode());
+
+        if (!hasVideoStateChanged) { return;}
+
+        updateCameraSelection(call);
+
+        if (isVideoCall) {
+            enterVideoMode(call.getVideoCall(), call.getVideoState());
+        } else if (isVideoMode()) {
+            exitVideoMode();
+        }
+    }
+
+    private void checkForCallStateChange(Call call) {
+        final boolean isVideoCall = CallUtils.isVideoCall(call);
+        final boolean hasCallStateChanged = mCurrentCallState != call.getState();
+
+        Log.d(this, "checkForCallStateChange: isVideoCall= " + isVideoCall
+                + " hasCallStateChanged=" +
+                hasCallStateChanged + " isVideoMode=" + isVideoMode());
+
+        if (!hasCallStateChanged) { return; }
+
+        final InCallCameraManager cameraManager = InCallPresenter.getInstance().
+                getInCallCameraManager();
+
+        String prevCameraId = cameraManager.getActiveCameraId();
+
+        updateCameraSelection(call);
+
+        String newCameraId = cameraManager.getActiveCameraId();
+
+        if (!Objects.equals(prevCameraId, newCameraId) && CallUtils.isActiveVideoCall(call)) {
+            enableCamera(call.getVideoCall(), true);
+        }
+    }
+
+    private void checkForCallSubstateChange(Call call) {
+        if (mCurrentCallSubstate != call.getCallSubstate()) {
+            VideoCallUi ui = getUi();
+            if (ui == null) {
+                Log.e(this, "Error VideoCallUi is null. Return.");
+                return;
             }
+            mCurrentCallSubstate = call.getCallSubstate();
+            // Display a call substate changed message on UI.
+            ui.showCallSubstateChanged(mCurrentCallSubstate);
+        }
+    }
+
+    private void cleanupSurfaces() {
+        final VideoCallUi ui = getUi();
+        if (ui == null) {
+            Log.w(this, "cleanupSurfaces");
+            return;
+        }
+        ui.cleanupSurfaces();
+    }
+
+    private void onPrimaryCallChanged(Call newPrimaryCall) {
+        final boolean isVideoCall = CallUtils.isVideoCall(newPrimaryCall);
+        final boolean isVideoMode = isVideoMode();
+
+        Log.d(this, "onPrimaryCallChanged: isVideoCall=" + isVideoCall + " isVideoMode="
+                + isVideoMode);
+
+        if (!isVideoCall && isVideoMode) {
+            // Terminate video mode if new primary call is not a video call
+            // and we are currently in video mode.
+            Log.d(this, "onPrimaryCallChanged: Exiting video mode...");
+            exitVideoMode();
+        } else if (isVideoCall) {
+            Log.d(this, "onPrimaryCallChanged: Entering video mode...");
+
+            updateCameraSelection(newPrimaryCall);
+            enterVideoMode(newPrimaryCall.getVideoCall(), newPrimaryCall.getVideoState());
+        }
+    }
+
+    private boolean isVideoMode() {
+        return mIsVideoMode;
+    }
+
+    private void updateCallCache(Call call) {
+        if (call == null) {
+            mCurrentVideoState = VideoProfile.VideoState.AUDIO_ONLY;
+            mCurrentCallSubstate = Connection.SUBSTATE_NONE;
+            mCurrentCallState = Call.State.INVALID;
+            mVideoCall = null;
+            mPrimaryCall = null;
+        } else {
+            mCurrentVideoState = call.getVideoState();
+            mCurrentCallSubstate = call.getCallSubstate();
+            mVideoCall = call.getVideoCall();
+            mCurrentCallState = call.getState();
+            mPrimaryCall = call;
         }
     }
 
@@ -352,120 +522,214 @@
      */
     @Override
     public void onDetailsChanged(Call call, android.telecom.Call.Details details) {
+        Log.d(this, " onDetailsChanged call=" + call + " details=" + details + " mPrimaryCall="
+                + mPrimaryCall);
         // If the details change is not for the currently active call no update is required.
         if (!call.equals(mPrimaryCall)) {
+            Log.d(this," onDetailsChanged: Details not for current active call so returning. ");
             return;
         }
 
-        checkForVideoStateChange();
+        updateVideoCall(call);
+        checkForCallSubstateChange(call);
+
+        updateCallCache(call);
+    }
+
+    private void updateVideoCall(Call call) {
+        checkForVideoCallChange(call);
+        checkForVideoStateChange(call);
+        checkForCallStateChange(call);
     }
 
     /**
      * Checks for a change to the video call and changes it if required.
      */
-    private void checkForVideoCallChange() {
-        VideoCall videoCall = mPrimaryCall.getTelecommCall().getVideoCall();
+    private void checkForVideoCallChange(Call call) {
+        final VideoCall videoCall = call.getTelecommCall().getVideoCall();
+        Log.d(this, "checkForVideoCallChange: videoCall=" + videoCall + " mVideoCall="
+                + mVideoCall);
         if (!Objects.equals(videoCall, mVideoCall)) {
-            changeVideoCall(videoCall);
+            changeVideoCall(call);
         }
     }
 
     /**
-     * Checks to see if the current video state has changed and updates the UI if required.
-     */
-    private void checkForVideoStateChange() {
-        boolean newVideoState = mPrimaryCall.isVideoCall(mContext);
-
-        // Check if video state changed
-        if (mIsVideoCall != newVideoState) {
-            mIsVideoCall = newVideoState;
-
-            if (mIsVideoCall) {
-                enterVideoMode();
-            } else {
-                exitVideoMode();
-            }
-        }
-    }
-
-    /**
-     * Handles a change to the video call.  Sets the surfaces on the previous call to null and sets
+     * Handles a change to the video call. Sets the surfaces on the previous call to null and sets
      * the surfaces on the new video call accordingly.
      *
      * @param videoCall The new video call.
      */
-    private void changeVideoCall(VideoCall videoCall) {
+    private void changeVideoCall(Call call) {
+        final VideoCall videoCall = call.getTelecommCall().getVideoCall();
+        Log.d(this, "changeVideoCall to videoCall=" + videoCall + " mVideoCall=" + mVideoCall);
         // Null out the surfaces on the previous video call.
         if (mVideoCall != null) {
-            mVideoCall.setDisplaySurface(null);
-            mVideoCall.setPreviewSurface(null);
+            // Log.d(this, "Null out the surfaces on the previous video call.");
+            // mVideoCall.setDisplaySurface(null);
+            // mVideoCall.setPreviewSurface(null);
         }
 
+        final boolean hasChanged = mVideoCall == null && videoCall != null;
+
         mVideoCall = videoCall;
+        if (mVideoCall == null || call == null) {
+            Log.d(this, "Video call or primary call is null. Return");
+            return;
+        }
+
+        if (CallUtils.isVideoCall(call) && hasChanged) {
+            enterVideoMode(call.getVideoCall(), call.getVideoState());
+        }
+    }
+
+    private static boolean isCameraRequired(int videoState) {
+        return VideoProfile.VideoState.isBidirectional(videoState) ||
+                VideoProfile.VideoState.isTransmissionEnabled(videoState);
+    }
+
+    private boolean isCameraRequired() {
+        return mPrimaryCall != null ? isCameraRequired(mPrimaryCall.getVideoState()) : false;
     }
 
     /**
      * Enters video mode by showing the video surfaces and making other adjustments (eg. audio).
      * TODO(vt): Need to adjust size and orientation of preview surface here.
      */
-    private void enterVideoMode() {
-        if (DEBUG) {
-            Log.i(TAG, "enterVideoMode");
-        }
+    private void enterVideoMode(VideoCall videoCall, int newVideoState) {
+        Log.d(this, "enterVideoMode videoCall= " + videoCall + " videoState: " + newVideoState);
         VideoCallUi ui = getUi();
         if (ui == null) {
+            Log.e(this, "Error VideoCallUi is null so returning");
             return;
         }
 
-        ui.showVideoUi(true);
+        showVideoUi(newVideoState);
         InCallPresenter.getInstance().setInCallAllowsOrientationChange(true);
 
         // Communicate the current camera to telephony and make a request for the camera
         // capabilities.
-        if (mVideoCall != null) {
-            // Do not reset the surfaces if we just restarted the activity due to an orientation
-            // change.
-            if (ui.isActivityRestart()) {
-                return;
+        if (videoCall != null) {
+            if (ui.isDisplayVideoSurfaceCreated()) {
+                Log.d(this, "Calling setDisplaySurface with " + ui.getDisplayVideoSurface());
+                videoCall.setDisplaySurface(ui.getDisplayVideoSurface());
             }
 
-            mPreviewSurfaceState = PreviewSurfaceState.CAMERA_SET;
-            InCallCameraManager cameraManager = InCallPresenter.getInstance().
-                    getInCallCameraManager();
-            mVideoCall.setCamera(cameraManager.getActiveCameraId());
-            mVideoCall.requestCameraCapabilities();
-            if (DEBUG) {
-                Log.i(TAG, "isDisplayVideoSurfacedCreated: " + ui.isDisplayVideoSurfaceCreated());
+            final int rotation = ui.getCurrentRotation();
+            if (rotation != VideoCallFragment.ORIENTATION_UNKNOWN) {
+                videoCall.setDeviceOrientation(InCallPresenter.toRotationAngle(rotation));
             }
-            if (ui.isDisplayVideoSurfaceCreated()) {
-                mVideoCall.setDisplaySurface(ui.getDisplayVideoSurface());
-            }
+
+            enableCamera(videoCall, isCameraRequired(newVideoState));
+        }
+        mCurrentVideoState = newVideoState;
+        updateAudioMode(true);
+
+        mIsVideoMode = true;
+    }
+
+    //TODO: Move this into Telecom. InCallUI should not be this close to audio functionality.
+    private void updateAudioMode(boolean enableSpeaker) {
+        if (!isSpeakerEnabledForVideoCalls()) {
+            Log.d(this, "Speaker is disabled. Can't update audio mode");
+            return;
         }
 
-        mPreVideoAudioMode = AudioModeProvider.getInstance().getAudioMode();
-        TelecomAdapter.getInstance().setAudioRoute(AudioState.ROUTE_SPEAKER);
+        final TelecomAdapter telecomAdapter = TelecomAdapter.getInstance();
+        final boolean isPrevAudioModeValid =
+            sPrevVideoAudioMode != AudioModeProvider.AUDIO_MODE_INVALID;
+
+        Log.d(this, "Is previous audio mode valid = " + isPrevAudioModeValid + " enableSpeaker is "
+            + enableSpeaker);
+
+        // Set audio mode to previous mode if enableSpeaker is false.
+        if (isPrevAudioModeValid && !enableSpeaker) {
+            telecomAdapter.setAudioRoute(sPrevVideoAudioMode);
+            sPrevVideoAudioMode = AudioModeProvider.AUDIO_MODE_INVALID;
+            return;
+        }
+
+        int currentAudioMode = AudioModeProvider.getInstance().getAudioMode();
+
+        // Set audio mode to speaker if enableSpeaker is true and bluetooth or headset are not
+        // connected and it's a video call.
+        if (!isAudioRouteEnabled(currentAudioMode,
+            AudioState.ROUTE_BLUETOOTH | AudioState.ROUTE_WIRED_HEADSET) &&
+            !isPrevAudioModeValid && enableSpeaker && CallUtils.isVideoCall(mPrimaryCall)) {
+            sPrevVideoAudioMode = currentAudioMode;
+
+            Log.d(this, "Routing audio to speaker");
+            telecomAdapter.setAudioRoute(AudioState.ROUTE_SPEAKER);
+        }
+    }
+
+    private static boolean isSpeakerEnabledForVideoCalls() {
+        return (SystemProperties.getInt(TelephonyProperties.PROPERTY_VIDEOCALL_AUDIO_OUTPUT,
+                PhoneConstants.AUDIO_OUTPUT_DEFAULT) ==
+                PhoneConstants.AUDIO_OUTPUT_ENABLE_SPEAKER);
+    }
+
+    private void enableCamera(VideoCall videoCall, boolean isCameraRequired) {
+        Log.d(this, "enableCamera: VideoCall=" + videoCall + " enabling=" + isCameraRequired);
+        if (videoCall == null) {
+            Log.w(this, "enableCamera: VideoCall is null.");
+            return;
+        }
+
+        if (isCameraRequired) {
+            InCallCameraManager cameraManager = InCallPresenter.getInstance().
+                    getInCallCameraManager();
+            videoCall.setCamera(cameraManager.getActiveCameraId());
+            mPreviewSurfaceState = PreviewSurfaceState.CAMERA_SET;
+
+            videoCall.requestCameraCapabilities();
+        } else {
+            mPreviewSurfaceState = PreviewSurfaceState.NONE;
+            videoCall.setCamera(null);
+        }
     }
 
     /**
-     * Exits video mode by hiding the video surfaces  and making other adjustments (eg. audio).
+     * Exits video mode by hiding the video surfaces and making other adjustments (eg. audio).
      */
     private void exitVideoMode() {
+        Log.d(this, "exitVideoMode");
+
+        InCallPresenter.getInstance().setInCallAllowsOrientationChange(false);
+
+        showVideoUi(VideoProfile.VideoState.AUDIO_ONLY);
+        enableCamera(mVideoCall, false);
+
+        Log.d(this, "exitVideoMode mIsFullScreen: " + mIsFullScreen);
+        if (mIsFullScreen) {
+            toggleFullScreen();
+        }
+
+        mIsVideoMode = false;
+    }
+
+    /**
+     * Show video Ui depends on video state.
+     */
+    private void showVideoUi(int videoState) {
         VideoCallUi ui = getUi();
         if (ui == null) {
+            Log.e(this, "showVideoUi, VideoCallUi is null returning");
             return;
         }
-        InCallPresenter.getInstance().setInCallAllowsOrientationChange(false);
-        ui.showVideoUi(false);
 
-        if (mVideoCall != null) {
-            // Also disable camera otherwise it will be already in use for next upgrade
-            mVideoCall.setCamera(null);
+        if (VideoProfile.VideoState.isBidirectional(videoState)) {
+            ui.showVideoViews(true, true);
+        } else if (VideoProfile.VideoState.isTransmissionEnabled(videoState)) {
+            ui.showVideoViews(true, false);
+        } else if (VideoProfile.VideoState.isReceptionEnabled(videoState)) {
+            ui.showVideoViews(false, true);
+        } else {
+            ui.hideVideoUi();
         }
 
-        if (mPreVideoAudioMode != AudioModeProvider.AUDIO_MODE_INVALID) {
-            TelecomAdapter.getInstance().setAudioRoute(mPreVideoAudioMode);
-            mPreVideoAudioMode = AudioModeProvider.AUDIO_MODE_INVALID;
-        }
+        InCallPresenter.getInstance().enableScreenTimeout(
+                VideoProfile.VideoState.isAudioOnly(videoState));
     }
 
     /**
@@ -493,11 +757,43 @@
      */
     @Override
     public void onUpdatePeerDimensions(Call call, int width, int height) {
+        Log.d(this, "onUpdatePeerDimensions: width= " + width + " height= " + height);
+        VideoCallUi ui = getUi();
+        if (ui == null) {
+            Log.e(this, "VideoCallUi is null. Bail out");
+            return;
+        }
+        if (!call.equals(mPrimaryCall)) {
+            Log.e(this, "Current call is not equal to primary call. Bail out");
+            return;
+        }
+
+        // Change size of display surface to match the peer aspect ratio
+        if (width > 0 && height > 0) {
+            setDisplayVideoSize(width, height);
+        }
+    }
+
+    /**
+     * Handles any video quality changes in the call.
+     *
+     * @param call The call which experienced a video quality change.
+     * @param videoQuality The new video call quality.
+     */
+    @Override
+    public void onVideoQualityChanged(Call call, int videoQuality) {
         if (!call.equals(mPrimaryCall)) {
             return;
         }
 
-        // TODO(vt): Change display surface aspect ratio.
+        VideoCallUi ui = getUi();
+        if (ui == null) {
+            Log.e(this, "Error VideoCallUi is null. Return.");
+            return;
+        }
+
+        // Display a video quality changed message on UI.
+        ui.showVideoQualityChanged(videoQuality);
     }
 
     /**
@@ -510,16 +806,21 @@
      */
     @Override
     public void onCameraDimensionsChange(Call call, int width, int height) {
+        Log.d(this, "onCameraDimensionsChange call=" + call + " width=" + width + " height="
+                + height);
         VideoCallUi ui = getUi();
         if (ui == null) {
+            Log.e(this, "onCameraDimensionsChange ui is null");
             return;
         }
 
         if (!call.equals(mPrimaryCall)) {
+            Log.e(this, "Call is not primary call");
             return;
         }
 
         mPreviewSurfaceState = PreviewSurfaceState.CAPABILITIES_RECEIVED;
+        ui.setPreviewSurfaceSize(width, height);
 
         // Configure the preview surface to the correct aspect ratio.
         float aspectRatio = 1.0f;
@@ -537,45 +838,105 @@
     }
 
     /**
-     * Handles hanges to the device orientation.
+     * Called when call session event is raised.
+     *
+     * @param event The call session event.
+     */
+    @Override
+    public void onCallSessionEvent(int event) {
+        Log.d(this, "onCallSessionEvent event =" + event);
+        VideoCallUi ui = getUi();
+        if (ui == null) {
+            Log.e(this, "onCallSessionEvent: VideoCallUi is null");
+            return;
+        }
+        ui.displayCallSessionEvent(event);
+    }
+
+    /**
+     * Handles a change to the call data usage
+     *
+     * @param dataUsage call data usage value
+     */
+    @Override
+    public void onCallDataUsageChange(long dataUsage) {
+        Log.d(this, "onCallDataUsageChange dataUsage=" + dataUsage);
+        VideoCallUi ui = getUi();
+        if (ui == null) {
+            Log.e(this, "onCallDataUsageChange: VideoCallUi is null");
+            return;
+        }
+        ui.setCallDataUsage(mContext, dataUsage);
+    }
+
+    /**
+     * Handles changes to the device orientation.
      * See: {@link Configuration.ORIENTATION_LANDSCAPE}, {@link Configuration.ORIENTATION_PORTRAIT}
      * @param orientation The device orientation.
      */
     @Override
     public void onDeviceOrientationChanged(int orientation) {
+        Log.d(this, "onDeviceOrientationChanged: orientation=" + orientation);
         mDeviceOrientation = orientation;
     }
 
     @Override
-    public void onUpgradeToVideoRequest(Call call) {
-        mPrimaryCall.setSessionModificationState(
-                Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST);
+    public void onUpgradeToVideoRequest(Call call, int videoState) {
+        Log.d(this, "onUpgradeToVideoRequest call = " + call + " new video state = " + videoState);
+        if (mPrimaryCall == null || !Call.areSame(mPrimaryCall, call)) {
+            Log.w(this, "UpgradeToVideoRequest received for non-primary call");
+        }
+
+        if (call == null) {
+            return;
+        }
+
+        call.setSessionModificationTo(videoState);
     }
 
     @Override
     public void onUpgradeToVideoSuccess(Call call) {
+        Log.d(this, "onUpgradeToVideoSuccess call=" + call);
         if (mPrimaryCall == null || !Call.areSame(mPrimaryCall, call)) {
+            Log.w(this, "UpgradeToVideoSuccess received for non-primary call");
+        }
+
+        if (call == null) {
             return;
         }
 
-        mPrimaryCall.setSessionModificationState(Call.SessionModificationState.NO_REQUEST);
+        call.setSessionModificationState(Call.SessionModificationState.NO_REQUEST);
     }
 
     @Override
-    public void onUpgradeToVideoFail(Call call) {
+    public void onUpgradeToVideoFail(int status, Call call) {
+        Log.d(this, "onUpgradeToVideoFail call=" + call);
         if (mPrimaryCall == null || !Call.areSame(mPrimaryCall, call)) {
+            Log.w(this, "UpgradeToVideoFail received for non-primary call");
+        }
+
+        if (call == null) {
             return;
         }
 
-        call.setSessionModificationState(Call.SessionModificationState.REQUEST_FAILED);
+        if (status == VideoProvider.SESSION_MODIFY_REQUEST_TIMED_OUT) {
+            call.setSessionModificationState(
+                    Call.SessionModificationState.UPGRADE_TO_VIDEO_REQUEST_TIMED_OUT);
+        } else {
+            call.setSessionModificationState(Call.SessionModificationState.REQUEST_FAILED);
 
-        // Start handler to change state from REQUEST_FAILED to NO_REQUEST after an interval.
-        mSessionModificationResetHandler.postDelayed(new Runnable() {
-            @Override
-            public void run() {
-                mPrimaryCall.setSessionModificationState(Call.SessionModificationState.NO_REQUEST);
-            }
-        }, SESSION_MODIFICATION_RESET_DELAY_MS);
+            final Call modifyCall = call;
+            // Start handler to change state from REQUEST_FAILED to NO_REQUEST after an interval.
+            mSessionModificationResetHandler.postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    if (modifyCall != null) {
+                        modifyCall
+                            .setSessionModificationState(Call.SessionModificationState.NO_REQUEST);
+                    }
+                }
+            }, SESSION_MODIFICATION_RESET_DELAY_MS);
+        }
     }
 
     @Override
@@ -611,16 +972,140 @@
     }
 
     /**
+     * Sets the display video surface size based on peer width and height
+     *
+     * @param width peer width
+     * @param height peer height
+     */
+
+    private void setDisplayVideoSize(int width, int height) {
+        Log.d(this, "setDisplayVideoSize:Received peer width=" + width + " peer height=" + height);
+        VideoCallUi ui = getUi();
+        if (ui == null) {
+            return;
+        }
+
+        // Get current display size
+        Point size = ui.getScreenSize();
+        Log.d("VideoCallPresenter", "setDisplayVideoSize: windowmgr width=" + size.x
+                + " windowmgr height=" + size.y);
+        if (size.y * width > size.x * height) {
+            // current display height is too much. Correct it
+            size.y = (int) (size.x * height / width);
+        } else if (size.y * width < size.x * height) {
+            // current display width is too much. Correct it
+            size.x = (int) (size.y * width / height);
+        }
+        ui.setDisplayVideoSize(size.x, size.y);
+    }
+
+    private static boolean isAudioRouteEnabled(int audioRoute, int audioRouteMask) {
+        return ((audioRoute & audioRouteMask) != 0);
+    }
+
+    private static void updateCameraSelection(Call call) {
+        Log.d(TAG, "updateCameraSelection: call=" + call);
+        Log.d(TAG, "updateCameraSelection: call=" + toSimpleString(call));
+
+        final Call activeCall = CallList.getInstance().getActiveCall();
+        int cameraDir = Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN;
+
+        // this function should never be called with null call object, however if it happens we
+        // should handle it gracefully.
+        if (call == null) {
+            cameraDir = Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN;
+            com.android.incallui.Log.e(TAG, "updateCameraSelection: Call object is null."
+                    + " Setting camera direction to default value (CAMERA_DIRECTION_UNKNOWN)");
+        }
+
+        // Clear camera direction if this is not a video call.
+        else if (CallUtils.isAudioCall(call)) {
+            cameraDir = Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN;
+            call.getVideoSettings().setCameraDir(cameraDir);
+        }
+
+        // If this is a waiting video call, default to active call's camera,
+        // since we don't want to change the current camera for waiting call
+        // without user's permission.
+        else if (CallUtils.isVideoCall(activeCall) && CallUtils.isIncomingVideoCall(call)) {
+            cameraDir = activeCall.getVideoSettings().getCameraDir();
+        }
+
+        // Infer the camera direction from the video state and store it,
+        // if this is an outgoing video call.
+        else if (CallUtils.isOutgoingVideoCall(call) && !isCameraDirectionSet(call) ) {
+            cameraDir = toCameraDirection(call.getVideoState());
+            call.getVideoSettings().setCameraDir(cameraDir);
+        }
+
+        // Use the stored camera dir if this is an outgoing video call for which camera direction
+        // is set.
+        else if (CallUtils.isOutgoingVideoCall(call)) {
+            cameraDir = call.getVideoSettings().getCameraDir();
+        }
+
+        // Infer the camera direction from the video state and store it,
+        // if this is an active video call and camera direction is not set.
+        else if (CallUtils.isActiveVideoCall(call) && !isCameraDirectionSet(call)) {
+            cameraDir = toCameraDirection(call.getVideoState());
+            call.getVideoSettings().setCameraDir(cameraDir);
+        }
+
+        // Use the stored camera dir if this is an active video call for which camera direction
+        // is set.
+        else if (CallUtils.isActiveVideoCall(call)) {
+            cameraDir = call.getVideoSettings().getCameraDir();
+        }
+
+        // For all other cases infer the camera direction but don't store it in the call object.
+        else {
+            cameraDir = toCameraDirection(call.getVideoState());
+        }
+
+        com.android.incallui.Log.d(TAG, "updateCameraSelection: Setting camera direction to " +
+                cameraDir + " Call=" + call);
+        final InCallCameraManager cameraManager = InCallPresenter.getInstance().
+                getInCallCameraManager();
+        cameraManager.setUseFrontFacingCamera(cameraDir ==
+                Call.VideoSettings.CAMERA_DIRECTION_FRONT_FACING);
+    }
+
+    private static int toCameraDirection(int videoState) {
+        return VideoProfile.VideoState.isTransmissionEnabled(videoState) &&
+                !VideoProfile.VideoState.isBidirectional(videoState)
+                ? Call.VideoSettings.CAMERA_DIRECTION_BACK_FACING
+                : Call.VideoSettings.CAMERA_DIRECTION_FRONT_FACING;
+    }
+
+    private static boolean isCameraDirectionSet(Call call) {
+        return CallUtils.isVideoCall(call) && call.getVideoSettings().getCameraDir()
+                    != Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN;
+    }
+
+    private static String toSimpleString(Call call) {
+        return call == null ? null : call.toSimpleString();
+    }
+
+    /**
      * Defines the VideoCallUI interactions.
      */
     public interface VideoCallUi extends Ui {
-        void showVideoUi(boolean show);
+        void showVideoViews(boolean showPreview, boolean showIncoming);
+        void hideVideoUi();
+        void showVideoQualityChanged(int videoQuality);
         boolean isDisplayVideoSurfaceCreated();
         boolean isPreviewVideoSurfaceCreated();
         Surface getDisplayVideoSurface();
         Surface getPreviewVideoSurface();
+        int getCurrentRotation();
         void setPreviewSize(int width, int height);
+        void setPreviewSurfaceSize(int width, int height);
+        void setDisplayVideoSize(int width, int height);
+        void setCallDataUsage(Context context, long dataUsage);
+        void displayCallSessionEvent(int event);
+        Point getScreenSize();
         void cleanupSurfaces();
         boolean isActivityRestart();
+        void showCallSubstateChanged(int callSubstate);
     }
 }
diff --git a/InCallUI/src/com/android/incallui/VideoPauseController.java b/InCallUI/src/com/android/incallui/VideoPauseController.java
new file mode 100644
index 0000000..727780e
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/VideoPauseController.java
@@ -0,0 +1,389 @@
+/* Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux Foundation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.incallui;
+
+import android.os.SystemProperties;
+import android.telecom.VideoProfile;
+import com.android.incallui.Call.State;
+import com.android.incallui.InCallPresenter.InCallState;
+import com.android.incallui.InCallPresenter.InCallStateListener;
+import com.android.incallui.InCallPresenter.IncomingCallListener;
+import com.android.incallui.InCallVideoCallListenerNotifier.SessionModificationListener;
+import com.android.internal.util.Preconditions;
+
+/**
+ * The class is responsible for generating video pause/resume request.
+ */
+class VideoPauseController implements InCallStateListener, IncomingCallListener,
+        SessionModificationListener {
+    private static final String TAG = "VideoCallPauseController:";
+
+    private class CallContext {
+        public CallContext(Call call) {
+            Preconditions.checkNotNull(call);
+            update(call);
+        }
+
+        public void update(Call call) {
+            mCall = Preconditions.checkNotNull(call);
+            mState = call.getState();
+            mId = call.getId();
+            mVideoState = call.getVideoState();
+        }
+
+        public int getState() {
+            return mState;
+        }
+
+        public String getId() {
+            return mId;
+        }
+
+        public int getVideoState() {
+            return mVideoState;
+        }
+
+        public String toString() {
+            return String.format("CallContext {CallId=%s, State=%s, VideoState=",
+                    mId, mState, mVideoState);
+        }
+
+        public Call getCall() {
+            return mCall;
+        }
+
+        private int mState = State.INVALID;
+        private String mId;
+        private int mVideoState;
+        private Call mCall;
+    }
+
+    private InCallPresenter mInCallPresenter;
+    private static VideoPauseController sVideoPauseController;
+
+    private CallContext mPrimaryCallContext = null; // Context of primary call, if any.
+    private boolean mIsInBackground = false; // True if UI is not visible, false otherwise.
+    private int mVideoPauseMode = VIDEO_PAUSE_MODE_DISABLED;
+
+    /**
+     * Stores current video pause mode.
+     * 0 - Video Pause is disabled.
+     * 1 - Video Pause is enabled.
+     */
+    private static final String PROPERTY_VIDEO_PAUSE_MODE = "persist.radio.videopause.mode";
+    private static int VIDEO_PAUSE_MODE_DISABLED = 0;
+    private static int VIDEO_PAUSE_MODE_ENABLED = 1;
+
+    private VideoPauseController() {
+        mVideoPauseMode = SystemProperties.getInt(PROPERTY_VIDEO_PAUSE_MODE,
+                VIDEO_PAUSE_MODE_DISABLED);
+        if (mVideoPauseMode != VIDEO_PAUSE_MODE_ENABLED) { // Validate the mode before using.
+            mVideoPauseMode = VIDEO_PAUSE_MODE_DISABLED;
+        }
+    }
+
+    /*package*/
+    static synchronized VideoPauseController getInstance() {
+        if (sVideoPauseController == null) {
+            sVideoPauseController = new VideoPauseController();
+        }
+        return sVideoPauseController;
+    }
+
+    public void setUp(InCallPresenter inCallPresenter) {
+        if (!isVideoPausedEnabled()) {
+            return;
+        }
+
+        log("setUp...");
+        mInCallPresenter = Preconditions.checkNotNull(inCallPresenter);
+        mInCallPresenter.addListener(this);
+        mInCallPresenter.addIncomingCallListener(this);
+        InCallVideoCallListenerNotifier.getInstance().addSessionModificationListener(this);
+    }
+
+    public void tearDown() {
+        if (!isVideoPausedEnabled()) {
+            return;
+        }
+
+        log("tearDown...");
+        InCallVideoCallListenerNotifier.getInstance().removeSessionModificationListener(this);
+        mInCallPresenter.removeListener(this);
+        mInCallPresenter.removeIncomingCallListener(this);
+        clear();
+    }
+
+    private void clear() {
+        mInCallPresenter = null;
+        mPrimaryCallContext = null;
+        mIsInBackground = false;
+    }
+
+    /**
+     * The function gets called when call state changes.
+     * @param state Phone state.
+     * @param callList List of current call.
+     */
+    @Override
+    public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
+        log("onStateChange, OldState=" + oldState + " NewState=" + newState);
+
+        Call call = null;
+        if (newState == InCallState.INCOMING) {
+            call = callList.getIncomingCall();
+        } else if (newState == InCallState.WAITING_FOR_ACCOUNT) {
+            call = callList.getWaitingForAccountCall();
+        } else if (newState == InCallState.PENDING_OUTGOING) {
+            call = callList.getPendingOutgoingCall();
+        } else if (newState == InCallState.OUTGOING) {
+            call = callList.getOutgoingCall();
+        } else {
+            call = callList.getActiveCall();
+        }
+
+        boolean hasPrimaryCallChanged = !areSame(call, mPrimaryCallContext);
+        boolean canVideoPause = CallUtils.canVideoPause(call);
+        log("onStateChange, hasPrimaryCallChanged=" + hasPrimaryCallChanged);
+        log("onStateChange, canVideoPause=" + canVideoPause);
+        log("onStateChange, IsInBackground=" + mIsInBackground);
+
+        if (hasPrimaryCallChanged) {
+            onPrimaryCallChanged(call);
+            return;
+        }
+
+        if (isOutgoing(mPrimaryCallContext) && canVideoPause && mIsInBackground) {
+            // Bring UI to foreground if outgoing request becomes active while UI is in
+            // background.
+            bringToForeground();
+        } else if (!isVideoCall(mPrimaryCallContext) && canVideoPause && mIsInBackground) {
+            // Bring UI to foreground if VoLTE call becomes active while UI is in
+            // background.
+            bringToForeground();
+        }
+
+        updatePrimaryCallContext(call);
+    }
+
+    private void onPrimaryCallChanged(Call call) {
+        log("onPrimaryCallChanged: New call = " + call);
+        log("onPrimaryCallChanged: Old call = " + mPrimaryCallContext);
+        log("onPrimaryCallChanged, IsInBackground=" + mIsInBackground);
+
+        Preconditions.checkState(!areSame(call, mPrimaryCallContext));
+        final boolean canVideoPause = CallUtils.canVideoPause(call);
+
+        if (isWaitingCall(mPrimaryCallContext) && canVideoPause && !mIsInBackground) {
+            // Send resume request for the active call, if user rejects incoming
+            // call and UI is in foreground.
+            sendRequest(call, true);
+        } else if (isWaitingCall(call) && canVideoPause(mPrimaryCallContext)) {
+            // Send pause request if there is an active video call, and we just received a new
+            // incoming call.
+            sendRequest(mPrimaryCallContext.getCall(), false);
+        } else if (isOutgoing(mPrimaryCallContext) && canVideoPause && !mIsInBackground) {
+            // Send resume request for the active call, if user ends outgoing call
+            // and UI is in foreground.
+            sendRequest(call, true);
+        }
+
+        updatePrimaryCallContext(call);
+    }
+
+    /**
+     * The function gets called when InCallUI receives a new incoming call.
+     */
+    @Override
+    public void onIncomingCall(InCallState oldState, InCallState newState, Call call) {
+        log("onIncomingCall, OldState=" + oldState + " NewState=" + newState + " Call=" + call);
+
+        if (areSame(call, mPrimaryCallContext)) {
+            return;
+        }
+
+        onPrimaryCallChanged(call);
+    }
+
+    private void updatePrimaryCallContext(Call call) {
+        if (call == null) {
+            mPrimaryCallContext = null;
+        } else if (mPrimaryCallContext != null) {
+            mPrimaryCallContext.update(call);
+        } else {
+            mPrimaryCallContext = new CallContext(call);
+        }
+    }
+
+    /**
+     * Called when UI goes in/out of the foreground.
+     * @param showing true if UI is in the foreground, false otherwise.
+     */
+    public void onUiShowing(boolean showing) {
+        if (!isVideoPausedEnabled() || mInCallPresenter == null) {
+            return;
+        }
+
+        final boolean notify = mInCallPresenter.getInCallState() == InCallState.INCALL;
+        if (showing) {
+            onResume(notify);
+        } else {
+            onPause(notify);
+        }
+    }
+
+    @Override
+    public void onUpgradeToVideoRequest(Call call, int videoState) {
+    }
+
+    @Override
+    public void onUpgradeToVideoSuccess(Call call) {
+    }
+
+    @Override
+    public void onUpgradeToVideoFail(int status, Call call) {
+        // TODO (ims-vt) Automatically bring in call ui to foreground.
+    }
+
+    @Override
+    public void onDowngradeToAudio(Call call) {
+    }
+
+    /**
+     * Called when UI becomes visible. This will send resume request for current video call, if any.
+     */
+    private void onResume(boolean notify) {
+        log("onResume: notify=" + notify);
+
+        mIsInBackground = false;
+        if (canVideoPause(mPrimaryCallContext) && notify) {
+            sendRequest(mPrimaryCallContext.getCall(), true);
+        } else {
+            log("onResume. Ignoring...");
+        }
+    }
+
+    /**
+     * Called when UI becomes invisible. This will send pause request for current video call, if any.
+     */
+    private void onPause(boolean notify) {
+        log("onPause: notify=" + notify);
+
+        mIsInBackground = true;
+        if (canVideoPause(mPrimaryCallContext) && notify) {
+            sendRequest(mPrimaryCallContext.getCall(), false);
+        } else {
+            log("onPause, Ignoring...");
+        }
+    }
+
+    private void bringToForeground() {
+        if (mInCallPresenter != null) {
+            log("Bringing UI to foreground");
+            mInCallPresenter.bringToForeground(false);
+        } else {
+            loge("InCallPresenter is null. Cannot bring UI to foreground");
+        }
+    }
+
+    /**
+     * Sends Pause/Resume request.
+     * @param call Call to be paused/resumed.
+     * @param resume If true resume request will be sent, otherwise pause request.
+     */
+    private void sendRequest(Call call, boolean resume) {
+        if (resume) {
+            log("sending resume request, call=" + call);
+            call.getVideoCall().sendSessionModifyRequest(CallUtils.makeVideoUnPauseProfile(call));
+        } else {
+            log("sending pause request, call=" + call);
+            call.getVideoCall().sendSessionModifyRequest(CallUtils.makeVideoPauseProfile(call));
+        }
+    }
+
+    private boolean isVideoPausedEnabled() {
+        return mVideoPauseMode != VIDEO_PAUSE_MODE_DISABLED;
+    }
+
+    private static boolean areSame(Call call, CallContext callContext) {
+        if (call == null && callContext == null) {
+            return true;
+        } else if (call == null || callContext == null) {
+            return false;
+        }
+        return call.getId().equals(callContext.getId());
+    }
+
+    private static boolean areSame(CallContext callContext, Call call) {
+        return areSame(call, callContext);
+    }
+
+    private static boolean canVideoPause(CallContext callContext) {
+        return isVideoCall(callContext) && callContext.getState() == Call.State.ACTIVE;
+    }
+
+    private static boolean isVideoCall(CallContext callContext) {
+        return callContext != null && VideoProfile.VideoState.isVideo(callContext.getVideoState());
+    }
+
+    /**
+     * Returns true if call is in incoming/waiting state, false otherwise.
+     */
+    private static boolean isWaitingCall(CallContext call) {
+        return call != null && (call.getState() == Call.State.CALL_WAITING
+                || call.getState() == Call.State.INCOMING);
+    }
+
+    private static boolean isWaitingCall(Call call) {
+        return call != null && (call.getState() == Call.State.CALL_WAITING
+                || call.getState() == Call.State.INCOMING);
+    }
+
+    /**
+     * Returns true if the call is outgoing, false otherwise
+     */
+    private static boolean isOutgoing(CallContext call) {
+        return call != null && Call.State.isDialing(call.getState());
+    }
+
+    /**
+     * Returns true if the call is on hold, false otherwise
+     */
+    private static boolean isHolding(CallContext call) {
+        return call != null && call.getState() == Call.State.ONHOLD;
+    }
+
+    private void log(String msg) {
+        Log.d(this, TAG + msg);
+    }
+
+    private void loge(String msg) {
+        Log.e(this, TAG + msg);
+    }
+}