Merge "Use ContactsUtil.FLAG_N_FEATURE in CallerInfoAsyncQuery" into ub-contactsdialer-b-dev
diff --git a/InCallUI/res/drawable/ic_lockscreen_decline_video_normal_layer.xml b/InCallUI/res/drawable/ic_lockscreen_decline_video_normal_layer.xml
index e247f0a..e3b89b9 100644
--- a/InCallUI/res/drawable/ic_lockscreen_decline_video_normal_layer.xml
+++ b/InCallUI/res/drawable/ic_lockscreen_decline_video_normal_layer.xml
@@ -28,7 +28,7 @@
         <bitmap
             android:gravity="center"
             android:src="@drawable/ic_toolbar_video_off"
-            android:tint="@color/glowpad_call_widget_normal_tint"
+            android:tint="@color/glowpad_end_call_widget_normal_tint"
             android:autoMirrored="true" />
     </item>
 </layer-list>
diff --git a/InCallUI/res/layout-land/video_call_views.xml b/InCallUI/res/layout-land/video_call_views.xml
deleted file mode 100644
index 8961ea4..0000000
--- a/InCallUI/res/layout-land/video_call_views.xml
+++ /dev/null
@@ -1,35 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
-  ~ 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
-  -->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent" >
-
-    <TextureView
-        android:id="@+id/incomingVideo"
-        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. -->
-    <TextureView
-        android:id="@+id/previewVideo"
-        android:layout_gravity="bottom|right"
-        android:layout_margin="@dimen/video_preview_margin"
-        android:layout_width="70dp"
-        android:layout_height="120dp" />
-</FrameLayout>
diff --git a/InCallUI/res/values/array.xml b/InCallUI/res/values/array.xml
index 7877ec8..8744d3e 100644
--- a/InCallUI/res/values/array.xml
+++ b/InCallUI/res/values/array.xml
@@ -76,10 +76,10 @@
         <item>@drawable/ic_lockscreen_answer_video</item>
     </array>
     <array name="incoming_call_widget_video_without_sms_target_descriptions">
-        <item>@string/description_target_answer_video_call</item>
+        <item>@string/description_target_answer_audio_call</item>
         <item>@null</item>
         <item>@string/description_target_decline</item>
-        <item>@string/description_target_answer_audio_call</item>
+        <item>@string/description_target_answer_video_call</item>
     </array>
     <array name="incoming_call_widget_video_without_sms_direction_descriptions">
         <item>@string/description_direction_right</item>
@@ -89,21 +89,21 @@
     </array>
 
     <!-- For video calls, if respond via SMS is enabled:
-         - Answer as video call (drag right)
+         - Answer as audio call (drag right)
          - Respond via SMS (drag up)
          - Decline (drag left)
-         - Answer as audio call (drag down) -->
+         - Answer as video call (drag down) -->
     <array name="incoming_call_widget_video_with_sms_targets">
-        <item>@drawable/ic_lockscreen_answer_video</item>
+        <item>@drawable/ic_lockscreen_answer</item>
         <item>@drawable/ic_lockscreen_text</item>
         <item>@drawable/ic_lockscreen_decline</item>
-        <item>@drawable/ic_lockscreen_answer</item>
+        <item>@drawable/ic_lockscreen_answer_video</item>
     </array>
     <array name="incoming_call_widget_video_with_sms_target_descriptions">
-        <item>@string/description_target_answer_video_call</item>
+        <item>@string/description_target_answer_audio_call</item>
         <item>@string/description_target_send_sms</item>
         <item>@string/description_target_decline</item>
-        <item>@string/description_target_answer_audio_call</item>
+        <item>@string/description_target_answer_video_call</item>
     </array>
     <array name="incoming_call_widget_video_with_sms_direction_descriptions">
         <item>@string/description_direction_right</item>
diff --git a/InCallUI/src/com/android/incallui/AnswerPresenter.java b/InCallUI/src/com/android/incallui/AnswerPresenter.java
index 02dbfca..76ca4a9 100644
--- a/InCallUI/src/com/android/incallui/AnswerPresenter.java
+++ b/InCallUI/src/com/android/incallui/AnswerPresenter.java
@@ -48,6 +48,7 @@
     @Override
     public void onUiShowing(boolean showing) {
         if (showing) {
+            CallList.getInstance().addListener(this);
             final CallList calls = CallList.getInstance();
             Call call;
             call = calls.getIncomingCall();
@@ -60,6 +61,7 @@
                 processVideoUpgradeRequestCall(call);
             }
         } else {
+            CallList.getInstance().removeListener(this);
             // This is necessary because the activity can be destroyed while an incoming call exists.
             // This happens when back button is pressed while incoming call is still being shown.
             if (mCallId != null) {
@@ -174,7 +176,7 @@
         CallList.getInstance().addCallUpdateListener(mCallId, this);
 
         final int currentVideoState = call.getVideoState();
-        final int modifyToVideoState = call.getModifyToVideoState();
+        final int modifyToVideoState = call.getRequestedVideoState();
 
         if (currentVideoState == modifyToVideoState) {
             Log.w(this, "processVideoUpgradeRequestCall: Video states are same. Return.");
@@ -280,7 +282,7 @@
 
         // Only present the user with the option to answer as a video call if the incoming call is
         // a bi-directional video call.
-        if (CallUtils.isBidirectionalVideoCall(call)) {
+        if (VideoUtils.isBidirectionalVideoCall(call)) {
             if (withSms) {
                 getUi().showTargets(AnswerFragment.TARGET_SET_FOR_VIDEO_WITH_SMS);
                 getUi().configureMessageDialog(textMsgs);
diff --git a/InCallUI/src/com/android/incallui/AudioModeProvider.java b/InCallUI/src/com/android/incallui/AudioModeProvider.java
index e7ffecc..961fb11 100644
--- a/InCallUI/src/com/android/incallui/AudioModeProvider.java
+++ b/InCallUI/src/com/android/incallui/AudioModeProvider.java
@@ -16,10 +16,10 @@
 
 package com.android.incallui;
 
-import android.telecom.CallAudioState;
-
 import com.google.common.collect.Lists;
 
+import com.android.dialer.compat.CallAudioStateCompat;
+
 import java.util.List;
 
 /**
@@ -30,19 +30,20 @@
     static final int AUDIO_MODE_INVALID = 0;
 
     private static AudioModeProvider sAudioModeProvider = new AudioModeProvider();
-    private int mAudioMode = CallAudioState.ROUTE_EARPIECE;
+    private int mAudioMode = CallAudioStateCompat.ROUTE_EARPIECE;
     private boolean mMuted = false;
-    private int mSupportedModes = CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_BLUETOOTH |
-        CallAudioState.ROUTE_WIRED_HEADSET | CallAudioState.ROUTE_SPEAKER;
+    private int mSupportedModes = CallAudioStateCompat.ROUTE_EARPIECE
+            | CallAudioStateCompat.ROUTE_BLUETOOTH | CallAudioStateCompat.ROUTE_WIRED_HEADSET
+            | CallAudioStateCompat.ROUTE_SPEAKER;
     private final List<AudioModeListener> mListeners = Lists.newArrayList();
 
     public static AudioModeProvider getInstance() {
         return sAudioModeProvider;
     }
 
-    public void onAudioStateChanged(CallAudioState audioState) {
-        onAudioModeChange(audioState.getRoute(), audioState.isMuted());
-        onSupportedAudioModeChange(audioState.getSupportedRouteMask());
+    public void onAudioStateChanged(boolean isMuted, int route, int supportedRouteMask) {
+        onAudioModeChange(route, isMuted);
+        onSupportedAudioModeChange(supportedRouteMask);
     }
 
     public void onAudioModeChange(int newMode, boolean muted) {
diff --git a/InCallUI/src/com/android/incallui/Call.java b/InCallUI/src/com/android/incallui/Call.java
index 7b2724a..9526e7d 100644
--- a/InCallUI/src/com/android/incallui/Call.java
+++ b/InCallUI/src/com/android/incallui/Call.java
@@ -364,10 +364,12 @@
     private int mSessionModificationState;
     private final List<String> mChildCallIds = new ArrayList<>();
     private final VideoSettings mVideoSettings = new VideoSettings();
+    private int mVideoState;
+
     /**
-     * mModifyToVideoState is used to store requested upgrade / downgrade video state
+     * mRequestedVideoState is used to store requested upgrade / downgrade video state
      */
-    private int mModifyToVideoState = VideoProfile.STATE_AUDIO_ONLY;
+    private int mRequestedVideoState = VideoProfile.STATE_AUDIO_ONLY;
 
     private InCallVideoCallCallback mVideoCallCallback;
     private String mChildNumber;
@@ -435,6 +437,7 @@
         if (mState != State.BLOCKED) {
             setState(translatedState);
             setDisconnectCause(mTelecomCall.getDetails().getDisconnectCause());
+            maybeCancelVideoUpgrade(mTelecomCall.getDetails().getVideoState());
         }
 
         if (mTelecomCall.getVideoCall() != null) {
@@ -523,6 +526,23 @@
         }
     }
 
+    /**
+     * Determines if a received upgrade to video request should be cancelled.  This can happen if
+     * another InCall UI responds to the upgrade to video request.
+     *
+     * @param newVideoState The new video state.
+     */
+    private void maybeCancelVideoUpgrade(int newVideoState) {
+        boolean isVideoStateChanged = mVideoState != newVideoState;
+
+        if (mSessionModificationState == SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST
+                && isVideoStateChanged) {
+
+            Log.v(this, "maybeCancelVideoUpgrade : cancelling upgrade notification");
+            setSessionModificationState(SessionModificationState.NO_REQUEST);
+        }
+        mVideoState = newVideoState;
+    }
     private static int translateState(int state) {
         switch (state) {
             case android.telecom.Call.STATE_NEW:
@@ -722,43 +742,43 @@
 
     public boolean isVideoCall(Context context) {
         return CallUtil.isVideoEnabled(context) &&
-                CallUtils.isVideoCall(getVideoState());
+                VideoUtils.isVideoCall(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.
+     * Handles incoming session modification requests.  Stores the pending video request and sets
+     * the session modification state to
+     * {@link SessionModificationState#RECEIVED_UPGRADE_TO_VIDEO_REQUEST} so that we can keep track
+     * of the fact the request was received.  Only upgrade requests require user confirmation and
+     * will be handled by this method.  The remote user can turn off their own camera without
+     * confirmation.
+     *
+     * @param videoState The requested video state.
      */
-    public void setSessionModificationTo(int videoState) {
-        Log.d(this, "setSessionModificationTo - video state= " + videoState);
+    public void setRequestedVideoState(int videoState) {
+        Log.d(this, "setRequestedVideoState - 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.w(this,"setRequestedVideoState - Clearing session modification state");
+            return;
         }
 
-        Log.d(this, "setSessionModificationTo - mSessionModificationState="
+        mSessionModificationState = Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST;
+        mRequestedVideoState = videoState;
+        CallList.getInstance().onUpgradeToVideo(this);
+
+        Log.d(this, "setRequestedVideoState - 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.
+     * Set the session modification state.  Used to keep track of pending video session modification
+     * operations and to inform listeners of these changes.
+     *
+     * @param state the new session modification state.
      */
     public void setSessionModificationState(int state) {
-        if (state == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
-            Log.e(this,
-                    "setSessionModificationState not valid for RECEIVED_UPGRADE_TO_VIDEO_REQUEST");
-            return;
-        }
-
         boolean hasChanged = mSessionModificationState != state;
         mSessionModificationState = state;
         Log.d(this, "setSessionModificationState " + state + " mSessionModificationState="
@@ -776,12 +796,13 @@
         mIsEmergencyCall = TelecomCallUtil.isEmergencyCall(mTelecomCall);
     }
 
-    private void setModifyToVideoState(int newVideoState) {
-        mModifyToVideoState = newVideoState;
-    }
-
-    public int getModifyToVideoState() {
-        return mModifyToVideoState;
+    /**
+     * Gets the video state which was requested via a session modification request.
+     *
+     * @return The video state.
+     */
+    public int getRequestedVideoState() {
+        return mRequestedVideoState;
     }
 
     public static boolean areSame(Call call1, Call call2) {
@@ -806,6 +827,11 @@
         return TextUtils.equals(call1.getNumber(), call2.getNumber());
     }
 
+    /**
+     *  Gets the current video session modification state.
+     *
+     * @return The session modification state.
+     */
     public int getSessionModificationState() {
         return mSessionModificationState;
     }
diff --git a/InCallUI/src/com/android/incallui/CallButtonFragment.java b/InCallUI/src/com/android/incallui/CallButtonFragment.java
index 1d32d8f..6538474 100644
--- a/InCallUI/src/com/android/incallui/CallButtonFragment.java
+++ b/InCallUI/src/com/android/incallui/CallButtonFragment.java
@@ -16,18 +16,28 @@
 
 package com.android.incallui;
 
-import static com.android.incallui.CallButtonFragment.Buttons.*;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_ADD_CALL;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_AUDIO;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_COUNT;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_DIALPAD;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_HOLD;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_MANAGE_VIDEO_CONFERENCE;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_MERGE;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_MUTE;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_PAUSE_VIDEO;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_SWAP;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_SWITCH_CAMERA;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_UPGRADE_TO_VIDEO;
 
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
 import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
 import android.graphics.drawable.RippleDrawable;
 import android.graphics.drawable.StateListDrawable;
 import android.os.Bundle;
-import android.telecom.CallAudioState;
 import android.util.SparseIntArray;
 import android.view.ContextThemeWrapper;
 import android.view.HapticFeedbackConstants;
@@ -43,6 +53,7 @@
 import android.widget.PopupMenu.OnMenuItemClickListener;
 
 import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
+import com.android.dialer.compat.CallAudioStateCompat;
 
 /**
  * Fragment for call control buttons
@@ -516,20 +527,20 @@
         Log.d(this, "  id: " + item.getItemId());
         Log.d(this, "  title: '" + item.getTitle() + "'");
 
-        int mode = CallAudioState.ROUTE_WIRED_OR_EARPIECE;
+        int mode = CallAudioStateCompat.ROUTE_WIRED_OR_EARPIECE;
 
         switch (item.getItemId()) {
             case R.id.audio_mode_speaker:
-                mode = CallAudioState.ROUTE_SPEAKER;
+                mode = CallAudioStateCompat.ROUTE_SPEAKER;
                 break;
             case R.id.audio_mode_earpiece:
             case R.id.audio_mode_wired_headset:
                 // InCallCallAudioState.ROUTE_EARPIECE means either the handset earpiece,
                 // or the wired headset (if connected.)
-                mode = CallAudioState.ROUTE_WIRED_OR_EARPIECE;
+                mode = CallAudioStateCompat.ROUTE_WIRED_OR_EARPIECE;
                 break;
             case R.id.audio_mode_bluetooth:
-                mode = CallAudioState.ROUTE_BLUETOOTH;
+                mode = CallAudioStateCompat.ROUTE_BLUETOOTH;
                 break;
             default:
                 Log.e(this, "onMenuItemClick:  unexpected View ID " + item.getItemId()
@@ -559,9 +570,9 @@
      */
     private void onAudioButtonClicked() {
         Log.d(this, "onAudioButtonClicked: " +
-                CallAudioState.audioRouteToString(getPresenter().getSupportedAudio()));
+                CallAudioStateCompat.audioRouteToString(getPresenter().getSupportedAudio()));
 
-        if (isSupported(CallAudioState.ROUTE_BLUETOOTH)) {
+        if (isSupported(CallAudioStateCompat.ROUTE_BLUETOOTH)) {
             showAudioModePopup();
         } else {
             getPresenter().toggleSpeakerphone();
@@ -596,8 +607,8 @@
      * are visible based on the supported audio formats.
      */
     private void updateAudioButtons(int supportedModes) {
-        final boolean bluetoothSupported = isSupported(CallAudioState.ROUTE_BLUETOOTH);
-        final boolean speakerSupported = isSupported(CallAudioState.ROUTE_SPEAKER);
+        final boolean bluetoothSupported = isSupported(CallAudioStateCompat.ROUTE_BLUETOOTH);
+        final boolean speakerSupported = isSupported(CallAudioStateCompat.ROUTE_SPEAKER);
 
         boolean audioButtonEnabled = false;
         boolean audioButtonChecked = false;
@@ -617,9 +628,9 @@
             showMoreIndicator = true;
 
             // Update desired layers:
-            if (isAudio(CallAudioState.ROUTE_BLUETOOTH)) {
+            if (isAudio(CallAudioStateCompat.ROUTE_BLUETOOTH)) {
                 showBluetoothIcon = true;
-            } else if (isAudio(CallAudioState.ROUTE_SPEAKER)) {
+            } else if (isAudio(CallAudioStateCompat.ROUTE_SPEAKER)) {
                 showSpeakerphoneIcon = true;
             } else {
                 showHandsetIcon = true;
@@ -638,7 +649,7 @@
 
             // The audio button *is* a toggle in this state, and indicated the
             // current state of the speakerphone.
-            audioButtonChecked = isAudio(CallAudioState.ROUTE_SPEAKER);
+            audioButtonChecked = isAudio(CallAudioStateCompat.ROUTE_SPEAKER);
             mAudioButton.setSelected(audioButtonChecked);
 
             // update desired layers:
@@ -699,20 +710,20 @@
 
         // If bluetooth is not supported, the audio buttion will toggle, so use the label "speaker".
         // Otherwise, use the label of the currently selected audio mode.
-        if (!isSupported(CallAudioState.ROUTE_BLUETOOTH)) {
+        if (!isSupported(CallAudioStateCompat.ROUTE_BLUETOOTH)) {
             stringId = R.string.audio_mode_speaker;
         } else {
             switch (mode) {
-                case CallAudioState.ROUTE_EARPIECE:
+                case CallAudioStateCompat.ROUTE_EARPIECE:
                     stringId = R.string.audio_mode_earpiece;
                     break;
-                case CallAudioState.ROUTE_BLUETOOTH:
+                case CallAudioStateCompat.ROUTE_BLUETOOTH:
                     stringId = R.string.audio_mode_bluetooth;
                     break;
-                case CallAudioState.ROUTE_WIRED_HEADSET:
+                case CallAudioStateCompat.ROUTE_WIRED_HEADSET:
                     stringId = R.string.audio_mode_wired_headset;
                     break;
-                case CallAudioState.ROUTE_SPEAKER:
+                case CallAudioStateCompat.ROUTE_SPEAKER:
                     stringId = R.string.audio_mode_speaker;
                     break;
             }
@@ -742,7 +753,7 @@
         // See comments below for the exact logic.
 
         final MenuItem speakerItem = menu.findItem(R.id.audio_mode_speaker);
-        speakerItem.setEnabled(isSupported(CallAudioState.ROUTE_SPEAKER));
+        speakerItem.setEnabled(isSupported(CallAudioStateCompat.ROUTE_SPEAKER));
         // TODO: Show speakerItem as initially "selected" if
         // speaker is on.
 
@@ -751,7 +762,7 @@
         final MenuItem earpieceItem = menu.findItem(R.id.audio_mode_earpiece);
         final MenuItem wiredHeadsetItem = menu.findItem(R.id.audio_mode_wired_headset);
 
-        final boolean usingHeadset = isSupported(CallAudioState.ROUTE_WIRED_HEADSET);
+        final boolean usingHeadset = isSupported(CallAudioStateCompat.ROUTE_WIRED_HEADSET);
         earpieceItem.setVisible(!usingHeadset);
         earpieceItem.setEnabled(!usingHeadset);
         wiredHeadsetItem.setVisible(usingHeadset);
@@ -761,7 +772,7 @@
         // bluetoothIndicatorOn are both false.
 
         final MenuItem bluetoothItem = menu.findItem(R.id.audio_mode_bluetooth);
-        bluetoothItem.setEnabled(isSupported(CallAudioState.ROUTE_BLUETOOTH));
+        bluetoothItem.setEnabled(isSupported(CallAudioStateCompat.ROUTE_BLUETOOTH));
         // TODO: Show bluetoothItem as initially "selected" if
         // bluetoothIndicatorOn is true.
 
diff --git a/InCallUI/src/com/android/incallui/CallButtonPresenter.java b/InCallUI/src/com/android/incallui/CallButtonPresenter.java
index 29cdd4d..5375b5b 100644
--- a/InCallUI/src/com/android/incallui/CallButtonPresenter.java
+++ b/InCallUI/src/com/android/incallui/CallButtonPresenter.java
@@ -16,23 +16,30 @@
 
 package com.android.incallui;
 
-import static com.android.incallui.CallButtonFragment.Buttons.*;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_ADD_CALL;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_AUDIO;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_DIALPAD;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_HOLD;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_MERGE;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_MUTE;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_PAUSE_VIDEO;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_SWAP;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_SWITCH_CAMERA;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_UPGRADE_TO_VIDEO;
 
 import android.content.Context;
 import android.os.Bundle;
-import android.telecom.CallAudioState;
 import android.telecom.InCallService.VideoCall;
 import android.telecom.VideoProfile;
 
+import com.android.dialer.compat.CallAudioStateCompat;
 import com.android.incallui.AudioModeProvider.AudioModeListener;
 import com.android.incallui.InCallCameraManager.Listener;
 import com.android.incallui.InCallPresenter.CanAddCallListener;
+import com.android.incallui.InCallPresenter.InCallDetailsListener;
 import com.android.incallui.InCallPresenter.InCallState;
 import com.android.incallui.InCallPresenter.InCallStateListener;
 import com.android.incallui.InCallPresenter.IncomingCallListener;
-import com.android.incallui.InCallPresenter.InCallDetailsListener;
-
-import java.util.Objects;
 
 /**
  * Logic for call buttons.
@@ -177,7 +184,7 @@
         // an update for onAudioMode().  This will make UI response immediate
         // if it turns out to be slow
 
-        Log.d(this, "Sending new Audio Mode: " + CallAudioState.audioRouteToString(mode));
+        Log.d(this, "Sending new Audio Mode: " + CallAudioStateCompat.audioRouteToString(mode));
         TelecomAdapter.getInstance().setAudioRoute(mode);
     }
 
@@ -186,7 +193,7 @@
      */
     public void toggleSpeakerphone() {
         // this function should not be called if bluetooth is available
-        if (0 != (CallAudioState.ROUTE_BLUETOOTH & getSupportedAudio())) {
+        if (0 != (CallAudioStateCompat.ROUTE_BLUETOOTH & getSupportedAudio())) {
 
             // It's clear the UI is wrong, so update the supported mode once again.
             Log.e(this, "toggling speakerphone not allowed when bluetooth supported.");
@@ -194,11 +201,11 @@
             return;
         }
 
-        int newMode = CallAudioState.ROUTE_SPEAKER;
+        int newMode = CallAudioStateCompat.ROUTE_SPEAKER;
 
         // if speakerphone is already on, change to wired/earpiece
-        if (getAudioMode() == CallAudioState.ROUTE_SPEAKER) {
-            newMode = CallAudioState.ROUTE_WIRED_OR_EARPIECE;
+        if (getAudioMode() == CallAudioStateCompat.ROUTE_SPEAKER) {
+            newMode = CallAudioStateCompat.ROUTE_WIRED_OR_EARPIECE;
         }
 
         setAudioMode(newMode);
@@ -266,7 +273,7 @@
             return;
         }
         int currVideoState = mCall.getVideoState();
-        int currUnpausedVideoState = CallUtils.getUnPausedVideoState(currVideoState);
+        int currUnpausedVideoState = VideoUtils.getUnPausedVideoState(currVideoState);
         currUnpausedVideoState |= VideoProfile.STATE_BIDIRECTIONAL;
 
         VideoProfile videoProfile = new VideoProfile(currUnpausedVideoState);
@@ -356,7 +363,7 @@
         Log.v(this, "updateButtonsState");
         final CallButtonUi ui = getUi();
 
-        final boolean isVideo = CallUtils.isVideoCall(call);
+        final boolean isVideo = VideoUtils.isVideoCall(call);
 
         // Common functionality (audio, hold, etc).
         // Show either HOLD or SWAP, but not both. If neither HOLD or SWAP is available:
@@ -387,7 +394,7 @@
         ui.showButton(BUTTON_UPGRADE_TO_VIDEO, showUpgradeToVideo);
         ui.showButton(BUTTON_SWITCH_CAMERA, isVideo);
         ui.showButton(BUTTON_PAUSE_VIDEO, isVideo);
-        ui.showButton(BUTTON_DIALPAD, !isVideo);
+        ui.showButton(BUTTON_DIALPAD, true);
         ui.showButton(BUTTON_MERGE, showMerge);
 
         ui.updateButtonStates();
diff --git a/InCallUI/src/com/android/incallui/CallCardFragment.java b/InCallUI/src/com/android/incallui/CallCardFragment.java
index 3c92a76..dbb2c9d 100644
--- a/InCallUI/src/com/android/incallui/CallCardFragment.java
+++ b/InCallUI/src/com/android/incallui/CallCardFragment.java
@@ -28,13 +28,12 @@
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
 import android.os.Bundle;
-import android.os.Trace;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Trace;
 import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
 import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
 import android.telecom.DisconnectCause;
-import android.telecom.VideoProfile;
 import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 import android.text.format.DateUtils;
@@ -46,7 +45,6 @@
 import android.view.ViewTreeObserver;
 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
 import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
 import android.widget.ImageButton;
@@ -57,6 +55,7 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
+import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
 import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
 import com.android.contacts.common.widget.FloatingActionButtonController;
 import com.android.phone.common.animation.AnimUtils;
@@ -184,6 +183,11 @@
     private boolean mCallStateLabelResetPending = false;
     private Handler mHandler;
 
+    /**
+     * Determines if secondary call info is populated in the secondary call info UI.
+     */
+    private boolean mHasSecondaryCallInfo = false;
+
     @Override
     public CallCardPresenter.CallCardUi getUi() {
         return this;
@@ -375,6 +379,7 @@
      */
     @Override
     public void setCallCardVisible(final boolean visible) {
+        Log.v(this, "setCallCardVisible : isVisible = " + visible);
         // When animating the hide/show of the views in a landscape layout, we need to take into
         // account whether we are in a left-to-right locale or a right-to-left locale and adjust
         // the animations accordingly.
@@ -396,9 +401,7 @@
             @Override
             public boolean onPreDraw() {
                 // We don't want to continue getting called.
-                if (observer.isAlive()) {
-                    observer.removeOnPreDrawListener(this);
-                }
+                getView().getViewTreeObserver().removeOnPreDrawListener(this);
 
                 float videoViewTranslation = 0f;
 
@@ -407,9 +410,8 @@
                     mPrimaryCallCardContainer.setTranslationY(visible ?
                             -mPrimaryCallCardContainer.getHeight() : 0);
 
-                    if (visible) {
-                        videoViewTranslation = videoView.getHeight() / 2 - spaceBesideCallCard / 2;
-                    }
+                    ViewGroup.LayoutParams p = videoView.getLayoutParams();
+                    videoViewTranslation = p.height / 2 - spaceBesideCallCard / 2;
                 }
 
                 // Perform animation of video view.
@@ -418,12 +420,10 @@
                         .setDuration(mVideoAnimationDuration);
                 if (mIsLandscape) {
                     videoViewAnimator
-                            .translationX(videoViewTranslation)
-                            .start();
+                            .translationX(visible ? videoViewTranslation : 0);
                 } else {
                     videoViewAnimator
-                            .translationY(videoViewTranslation)
-                            .start();
+                            .translationY(visible ? videoViewTranslation : 0);
                 }
                 videoViewAnimator.start();
 
@@ -496,7 +496,7 @@
             mPrimaryName.setText(null);
         } else {
             mPrimaryName.setText(nameIsNumber
-                    ? PhoneNumberUtils.createTtsSpannable(name)
+                    ? PhoneNumberUtilsCompat.createTtsSpannable(name)
                     : name);
 
             // Set direction of the name field
@@ -529,7 +529,7 @@
             mPhoneNumber.setText(null);
             mPhoneNumber.setVisibility(View.GONE);
         } else {
-            mPhoneNumber.setText(PhoneNumberUtils.createTtsSpannable(number));
+            mPhoneNumber.setText(PhoneNumberUtilsCompat.createTtsSpannable(number));
             mPhoneNumber.setVisibility(View.VISIBLE);
             mPhoneNumber.setTextDirection(View.TEXT_DIRECTION_LTR);
         }
@@ -586,21 +586,22 @@
 
     @Override
     public void setSecondary(boolean show, String name, boolean nameIsNumber, String label,
-            String providerLabel, boolean isConference, boolean isVideoCall) {
-
-        if (show != mSecondaryCallInfo.isShown()) {
-            updateFabPositionForSecondaryCallInfo();
-        }
+            String providerLabel, boolean isConference, boolean isVideoCall, boolean isFullscreen) {
 
         if (show) {
+            mHasSecondaryCallInfo = true;
             boolean hasProvider = !TextUtils.isEmpty(providerLabel);
-            showAndInitializeSecondaryCallInfo(hasProvider);
+            initializeSecondaryCallInfo(hasProvider);
+
+            // Do not show the secondary caller info in fullscreen mode, but ensure it is populated
+            // in case fullscreen mode is exited in the future.
+            setSecondaryInfoVisible(!isFullscreen);
 
             mSecondaryCallConferenceCallIcon.setVisibility(isConference ? View.VISIBLE : View.GONE);
             mSecondaryCallVideoCallIcon.setVisibility(isVideoCall ? View.VISIBLE : View.GONE);
 
             mSecondaryCallName.setText(nameIsNumber
-                    ? PhoneNumberUtils.createTtsSpannable(name)
+                    ? PhoneNumberUtilsCompat.createTtsSpannable(name)
                     : name);
             if (hasProvider) {
                 mSecondaryCallProviderLabel.setText(providerLabel);
@@ -612,10 +613,92 @@
             }
             mSecondaryCallName.setTextDirection(nameDirection);
         } else {
-            mSecondaryCallInfo.setVisibility(View.GONE);
+            mHasSecondaryCallInfo = false;
+            setSecondaryInfoVisible(false);
         }
     }
 
+    /**
+     * Sets the visibility of the secondary caller info box.  Note, if the {@code visible} parameter
+     * is passed in {@code true}, and there is no secondary caller info populated (as determined by
+     * {@code mHasSecondaryCallInfo}, the secondary caller info box will not be shown.
+     *
+     * @param visible {@code true} if the secondary caller info should be shown, {@code false}
+     *      otherwise.
+     */
+    @Override
+    public void setSecondaryInfoVisible(final boolean visible) {
+        boolean wasVisible = mSecondaryCallInfo.isShown();
+        final boolean isVisible = visible && mHasSecondaryCallInfo;
+        Log.v(this, "setSecondaryInfoVisible: wasVisible = " + wasVisible + " isVisible = "
+                + isVisible);
+
+        // If visibility didn't change, nothing to do.
+        if (wasVisible == isVisible) {
+            return;
+        }
+
+        // If we are showing the secondary info, we need to show it before animating so that its
+        // height will be determined on layout.
+        if (isVisible) {
+            mSecondaryCallInfo.setVisibility(View.VISIBLE);
+        }
+
+        updateFabPositionForSecondaryCallInfo();
+        // We need to translate the secondary caller info, but we need to know its position after
+        // the layout has occurred so use a {@code ViewTreeObserver}.
+        final ViewTreeObserver observer = getView().getViewTreeObserver();
+
+        observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
+            @Override
+            public boolean onPreDraw() {
+                // We don't want to continue getting called.
+                getView().getViewTreeObserver().removeOnPreDrawListener(this);
+
+                // Get the height of the secondary call info now, and then re-hide the view prior
+                // to doing the actual animation.
+                int secondaryHeight = mSecondaryCallInfo.getHeight();
+                if (isVisible) {
+                    mSecondaryCallInfo.setVisibility(View.GONE);
+                }
+                Log.v(this, "setSecondaryInfoVisible: secondaryHeight = " + secondaryHeight);
+
+                // Set the position of the secondary call info card to its starting location.
+                mSecondaryCallInfo.setTranslationY(visible ? secondaryHeight : 0);
+
+                // Animate the secondary card info slide up/down as it appears and disappears.
+                ViewPropertyAnimator secondaryInfoAnimator = mSecondaryCallInfo.animate()
+                        .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
+                        .setDuration(mVideoAnimationDuration)
+                        .translationY(isVisible ? 0 : secondaryHeight)
+                        .setListener(new AnimatorListenerAdapter() {
+                            @Override
+                            public void onAnimationEnd(Animator animation) {
+                                if (!isVisible) {
+                                    mSecondaryCallInfo.setVisibility(View.GONE);
+                                }
+                            }
+
+                            @Override
+                            public void onAnimationStart(Animator animation) {
+                                if (isVisible) {
+                                    mSecondaryCallInfo.setVisibility(View.VISIBLE);
+                                }
+                            }
+                        });
+                secondaryInfoAnimator.start();
+
+                // Notify listeners of a change in the visibility of the secondary info. This is
+                // important when in a video call so that the video call presenter can shift the
+                // video preview up or down to accommodate the secondary caller info.
+                InCallPresenter.getInstance().notifySecondaryCallerInfoVisibilityChanged(visible,
+                        secondaryHeight);
+
+                return true;
+            }
+        });
+    }
+
     @Override
     public void setCallState(
             int state,
@@ -701,7 +784,7 @@
             mCallStateIcon.setVisibility(View.GONE);
         }
 
-        if (CallUtils.isVideoCall(videoState)
+        if (VideoUtils.isVideoCall(videoState)
                 || (state == Call.State.ACTIVE && sessionModificationState
                         == Call.SessionModificationState.WAITING_FOR_RESPONSE)) {
             mCallStateVideoCallIcon.setVisibility(View.VISIBLE);
@@ -953,7 +1036,7 @@
                 } else if (sessionModificationState
                         == Call.SessionModificationState.RECEIVED_UPGRADE_TO_VIDEO_REQUEST) {
                     callStateLabel = context.getString(R.string.card_title_video_call_requesting);
-                } else if (CallUtils.isVideoCall(videoState)) {
+                } else if (VideoUtils.isVideoCall(videoState)) {
                     callStateLabel = context.getString(R.string.card_title_video_call);
                 }
                 break;
@@ -977,7 +1060,7 @@
                     callStateLabel = label;
                 } else if (isAccount) {
                     callStateLabel = context.getString(R.string.incoming_via_template, label);
-                } else if (CallUtils.isVideoCall(videoState)) {
+                } else if (VideoUtils.isVideoCall(videoState)) {
                     callStateLabel = context.getString(R.string.notification_incoming_video_call);
                 } else {
                     callStateLabel = context.getString(R.string.card_title_incoming_call);
@@ -1007,9 +1090,7 @@
         return new CallStateLabel(callStateLabel, isAutoDismissing);
     }
 
-    private void showAndInitializeSecondaryCallInfo(boolean hasProvider) {
-        mSecondaryCallInfo.setVisibility(View.VISIBLE);
-
+    private void initializeSecondaryCallInfo(boolean hasProvider) {
         // mSecondaryCallName is initialized here (vs. onViewCreated) because it is inaccessible
         // until mSecondaryCallInfo is inflated in the call above.
         if (mSecondaryCallName == null) {
diff --git a/InCallUI/src/com/android/incallui/CallCardPresenter.java b/InCallUI/src/com/android/incallui/CallCardPresenter.java
index ba5823d..170e785 100644
--- a/InCallUI/src/com/android/incallui/CallCardPresenter.java
+++ b/InCallUI/src/com/android/incallui/CallCardPresenter.java
@@ -83,6 +83,7 @@
     private boolean mSpinnerShowing = false;
     private boolean mHasShownToast = false;
     private InCallContactInteractions mInCallContactInteractions;
+    private boolean mIsFullscreen = false;
 
     public static class ContactLookupCallback implements ContactInfoCacheCallback {
         private final WeakReference<CallCardPresenter> mCallCardPresenter;
@@ -775,7 +776,7 @@
         if (mSecondary == null) {
             // Clear the secondary display info.
             ui.setSecondary(false, null, false, null, null, false /* isConference */,
-                    false /* isVideoCall */);
+                    false /* isVideoCall */, mIsFullscreen);
             return;
         }
 
@@ -787,7 +788,8 @@
                     null /* label */,
                     getCallProviderLabel(mSecondary),
                     true /* isConference */,
-                    mSecondary.isVideoCall(mContext));
+                    mSecondary.isVideoCall(mContext),
+                    mIsFullscreen);
         } else if (mSecondaryContactInfo != null) {
             Log.d(TAG, "updateSecondaryDisplayInfo() " + mSecondaryContactInfo);
             String name = getNameForCall(mSecondaryContactInfo);
@@ -799,11 +801,12 @@
                     mSecondaryContactInfo.label,
                     getCallProviderLabel(mSecondary),
                     false /* isConference */,
-                    mSecondary.isVideoCall(mContext));
+                    mSecondary.isVideoCall(mContext),
+                    mIsFullscreen);
         } else {
             // Clear the secondary display info.
             ui.setSecondary(false, null, false, null, null, false /* isConference */,
-                    false /* isVideoCall */);
+                    false /* isVideoCall */, mIsFullscreen);
         }
     }
 
@@ -955,11 +958,18 @@
      */
     @Override
     public void onFullscreenModeChanged(boolean isFullscreenMode) {
+        mIsFullscreen = isFullscreenMode;
         final CallCardUi ui = getUi();
         if (ui == null) {
             return;
         }
         ui.setCallCardVisible(!isFullscreenMode);
+        ui.setSecondaryInfoVisible(!isFullscreenMode);
+    }
+
+    @Override
+    public void onSecondaryCallerInfoVisibilityChanged(boolean isVisible, int height) {
+        // No-op - the Call Card is the origin of this event.
     }
 
     private boolean isPrimaryCallActive() {
@@ -1061,7 +1071,9 @@
         void setPrimary(String number, String name, boolean nameIsNumber, String label,
                 Drawable photo, boolean isSipCall, boolean isContactPhotoShown);
         void setSecondary(boolean show, String name, boolean nameIsNumber, String label,
-                String providerLabel, boolean isConference, boolean isVideoCall);
+                String providerLabel, boolean isConference, boolean isVideoCall,
+                boolean isFullscreen);
+        void setSecondaryInfoVisible(boolean visible);
         void setCallState(int state, int videoState, int sessionModificationState,
                 DisconnectCause disconnectCause, String connectionLabel,
                 Drawable connectionIcon, String gatewayNumber, boolean isWifi,
diff --git a/InCallUI/src/com/android/incallui/CallList.java b/InCallUI/src/com/android/incallui/CallList.java
index 37ae14b..0f31f72 100644
--- a/InCallUI/src/com/android/incallui/CallList.java
+++ b/InCallUI/src/com/android/incallui/CallList.java
@@ -569,9 +569,10 @@
             // Second, ensure a VideoCall is set on the call so that the change can be sent to the
             // provider (a VideoCall can be present for a call that does not currently have video,
             // but can be upgraded to video).
+
             // NOTE: is it necessary to use this order because getVideoCall references the class
             // VideoProfile which is not available on APIs <23 (M).
-            if (CallUtils.isVideoCall(call) && call.getVideoCall() != null) {
+            if (VideoUtils.isVideoCall(call) && call.getVideoCall() != null) {
                 call.getVideoCall().setDeviceOrientation(rotation);
             }
         }
diff --git a/InCallUI/src/com/android/incallui/CallUtils.java b/InCallUI/src/com/android/incallui/CallUtils.java
deleted file mode 100644
index 6eb1a05..0000000
--- a/InCallUI/src/com/android/incallui/CallUtils.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/* 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.android.dialer.compat.DialerCompatUtils;
-
-import com.google.common.base.Preconditions;
-
-public class CallUtils {
-
-    public static boolean isVideoCall(Call call) {
-        return call != null && isVideoCall(call.getVideoState());
-    }
-
-    public static boolean isVideoCall(int videoState) {
-        if (!DialerCompatUtils.isVideoCompatible()) {
-            return false;
-        }
-
-        return VideoProfile.isTransmissionEnabled(videoState)
-                || VideoProfile.isReceptionEnabled(videoState);
-    }
-
-    public static boolean isBidirectionalVideoCall(Call call) {
-        if (!DialerCompatUtils.isVideoCompatible()) {
-            return false;
-        }
-
-        return VideoProfile.isBidirectional(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.SELECT_PHONE_ACCOUNT;
-    }
-
-    public static boolean isAudioCall(Call call) {
-        if (!DialerCompatUtils.isVideoCompatible()) {
-            return true;
-        }
-
-        return call != null && VideoProfile.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.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.STATE_PAUSED);
-    }
-
-    public static int getPausedVideoState(int videoState) {
-        return videoState | VideoProfile.STATE_PAUSED;
-    }
-
-}
diff --git a/InCallUI/src/com/android/incallui/ConferenceParticipantListAdapter.java b/InCallUI/src/com/android/incallui/ConferenceParticipantListAdapter.java
index a595c43..ad8de74 100644
--- a/InCallUI/src/com/android/incallui/ConferenceParticipantListAdapter.java
+++ b/InCallUI/src/com/android/incallui/ConferenceParticipantListAdapter.java
@@ -20,7 +20,6 @@
 
 import android.content.Context;
 import android.net.Uri;
-import android.telephony.PhoneNumberUtils;
 import android.text.BidiFormatter;
 import android.text.TextDirectionHeuristics;
 import android.text.TextUtils;
@@ -34,6 +33,7 @@
 
 import com.android.contacts.common.ContactPhotoManager;
 import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
+import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
 import com.android.contacts.common.preference.ContactsPreferences;
 import com.android.contacts.common.util.ContactDisplayUtils;
 import com.android.incallui.ContactInfoCache.ContactCacheEntry;
@@ -438,7 +438,7 @@
             numberTypeTextView.setVisibility(View.GONE);
         } else {
             numberTextView.setVisibility(View.VISIBLE);
-            numberTextView.setText(PhoneNumberUtils.createTtsSpannable(
+            numberTextView.setText(PhoneNumberUtilsCompat.createTtsSpannable(
                     BidiFormatter.getInstance().unicodeWrap(
                             callerNumber, TextDirectionHeuristics.LTR)));
             numberTypeTextView.setVisibility(View.VISIBLE);
diff --git a/InCallUI/src/com/android/incallui/DialpadFragment.java b/InCallUI/src/com/android/incallui/DialpadFragment.java
index 371f5c5..ab44cf2 100644
--- a/InCallUI/src/com/android/incallui/DialpadFragment.java
+++ b/InCallUI/src/com/android/incallui/DialpadFragment.java
@@ -20,7 +20,6 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
-import android.telephony.PhoneNumberUtils;
 import android.text.Editable;
 import android.text.method.DialerKeyListener;
 import android.util.AttributeSet;
@@ -34,6 +33,7 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import com.android.contacts.common.compat.PhoneNumberUtilsCompat;
 import com.android.phone.common.dialpad.DialpadKeyButton;
 import com.android.phone.common.dialpad.DialpadView;
 
@@ -483,7 +483,7 @@
      * @param text Text to set Dialpad EditText to.
      */
     public void setDtmfText(String text) {
-        mDtmfDialerField.setText(PhoneNumberUtils.createTtsSpannable(text));
+        mDtmfDialerField.setText(PhoneNumberUtilsCompat.createTtsSpannable(text));
     }
 
     @Override
diff --git a/InCallUI/src/com/android/incallui/InCallActivity.java b/InCallUI/src/com/android/incallui/InCallActivity.java
index cbcf50c..2eab7d3 100644
--- a/InCallUI/src/com/android/incallui/InCallActivity.java
+++ b/InCallUI/src/com/android/incallui/InCallActivity.java
@@ -82,6 +82,10 @@
     private static final String TAG_ANSWER_FRAGMENT = "tag_answer_fragment";
     private static final String TAG_SELECT_ACCT_FRAGMENT = "tag_select_acct_fragment";
 
+    private static final int DIALPAD_REQUEST_NONE = 1;
+    private static final int DIALPAD_REQUEST_SHOW = 2;
+    private static final int DIALPAD_REQUEST_HIDE = 3;
+
     private CallButtonFragment mCallButtonFragment;
     private CallCardFragment mCallCardFragment;
     private AnswerFragment mAnswerFragment;
@@ -90,11 +94,15 @@
     private FragmentManager mChildFragmentManager;
 
     private AlertDialog mDialog;
+    private InCallOrientationEventListener mInCallOrientationEventListener;
 
     /**
-     * Use to pass 'showDialpad' from {@link #onNewIntent} to {@link #onResume}
+     * Used to indicate whether the dialpad should be hidden or shown {@link #onResume}.
+     * {@code #DIALPAD_REQUEST_SHOW} indicates that the dialpad should be shown.
+     * {@code #DIALPAD_REQUEST_HIDE} indicates that the dialpad should be hidden.
+     * {@code #DIALPAD_REQUEST_NONE} indicates no change should be made to dialpad visibility.
      */
-    private boolean mShowDialpadRequested;
+    private int mShowDialpadRequest = DIALPAD_REQUEST_NONE;
 
     /**
      * Use to determine if the dialpad should be animated on show.
@@ -141,16 +149,6 @@
         }
     };
 
-    /**
-     * Listener for orientation changes.
-     */
-    private OrientationEventListener mOrientationEventListener;
-
-    /**
-     * Used to determine if a change in rotation has occurred.
-     */
-    private static int sPreviousRotation = -1;
-
     @Override
     protected void onCreate(Bundle icicle) {
         Log.d(this, "onCreate()...  this = " + this);
@@ -202,13 +200,24 @@
 
         mSlideOut.setAnimationListener(mSlideOutListener);
 
+        // If the dialpad fragment already exists, retrieve it.  This is important when rotating as
+        // we will not be able to hide or show the dialpad after the rotation otherwise.
+        Fragment existingFragment =
+                getFragmentManager().findFragmentByTag(DialpadFragment.class.getName());
+        if (existingFragment != null) {
+            mDialpadFragment = (DialpadFragment) existingFragment;
+        }
+
         if (icicle != null) {
             // If the dialpad was shown before, set variables indicating it should be shown and
             // populated with the previous DTMF text.  The dialpad is actually shown and populated
             // in onResume() to ensure the hosting CallCardFragment has been inflated and is ready
             // to receive it.
-            mShowDialpadRequested = icicle.getBoolean(SHOW_DIALPAD_EXTRA);
-            mAnimateDialpadOnShow = false;
+            if (icicle.containsKey(SHOW_DIALPAD_EXTRA)) {
+                boolean showDialpad = icicle.getBoolean(SHOW_DIALPAD_EXTRA);
+                mShowDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_HIDE;
+                mAnimateDialpadOnShow = false;
+            }
             mDtmfText = icicle.getString(DIALPAD_TEXT_EXTRA);
 
             SelectPhoneAccountDialogFragment dialogFragment = (SelectPhoneAccountDialogFragment)
@@ -217,39 +226,7 @@
                 dialogFragment.setListener(mSelectAcctListener);
             }
         }
-
-        mOrientationEventListener = new OrientationEventListener(this,
-                SensorManager.SENSOR_DELAY_NORMAL) {
-            @Override
-            public void onOrientationChanged(int orientation) {
-                // Device is flat, don't change orientation.
-                if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
-                    return;
-                }
-
-                int newRotation = Surface.ROTATION_0;
-                // We only shift if we're within 22.5 (23) degrees of the target
-                // orientation. This avoids flopping back and forth when holding
-                // the device at 45 degrees or so.
-                if (orientation >= 337 || orientation <= 23) {
-                    newRotation = Surface.ROTATION_0;
-                } else if (orientation >= 67 && orientation <= 113) {
-                    // Why not 90? Because screen and sensor orientation are
-                    // reversed.
-                    newRotation = Surface.ROTATION_270;
-                } else if (orientation >= 157 && orientation <= 203) {
-                    newRotation = Surface.ROTATION_180;
-                } else if (orientation >= 247 && orientation <= 293) {
-                    newRotation = Surface.ROTATION_90;
-                }
-
-                // Orientation is the current device orientation in degrees.  Ultimately we want
-                // the rotation (in fixed 90 degree intervals).
-                if (newRotation != sPreviousRotation) {
-                    doOrientationChanged(newRotation);
-                }
-            }
-        };
+        mInCallOrientationEventListener = new InCallOrientationEventListener(this);
 
         Log.d(this, "onCreate(): exit");
     }
@@ -270,16 +247,10 @@
         Log.d(this, "onStart()...");
         super.onStart();
 
-        if (mOrientationEventListener.canDetectOrientation()) {
-            Log.v(this, "Orientation detection enabled.");
-            mOrientationEventListener.enable();
-        } else {
-            Log.v(this, "Orientation detection disabled.");
-            mOrientationEventListener.disable();
-        }
-
         // setting activity should be last thing in setup process
         InCallPresenter.getInstance().setActivity(this);
+        enableInCallOrientationEventListener(getRequestedOrientation() ==
+               InCallOrientationEventListener.FULL_SENSOR_SCREEN_ORIENTATION);
 
         InCallPresenter.getInstance().onActivityStarted();
     }
@@ -292,16 +263,31 @@
         InCallPresenter.getInstance().setThemeColors();
         InCallPresenter.getInstance().onUiShowing(true);
 
-        if (mShowDialpadRequested) {
-            mCallButtonFragment.displayDialpad(true /* show */,
-                    mAnimateDialpadOnShow /* animate */);
-            mShowDialpadRequested = false;
-            mAnimateDialpadOnShow = false;
+        // Clear fullscreen state onResume; the stored value may not match reality.
+        InCallPresenter.getInstance().clearFullscreen();
 
-            if (mDialpadFragment != null) {
-                mDialpadFragment.setDtmfText(mDtmfText);
-                mDtmfText = null;
+        // If there is a pending request to show or hide the dialpad, handle that now.
+        if (mShowDialpadRequest != DIALPAD_REQUEST_NONE) {
+            if (mShowDialpadRequest == DIALPAD_REQUEST_SHOW) {
+                // Exit fullscreen so that the user has access to the dialpad hide/show button and
+                // can hide the dialpad.  Important when showing the dialpad from within dialer.
+                InCallPresenter.getInstance().setFullScreen(false, true /* force */);
+
+                mCallButtonFragment.displayDialpad(true /* show */,
+                        mAnimateDialpadOnShow /* animate */);
+                mAnimateDialpadOnShow = false;
+
+                if (mDialpadFragment != null) {
+                    mDialpadFragment.setDtmfText(mDtmfText);
+                    mDtmfText = null;
+                }
+            } else {
+                Log.v(this, "onResume : force hide dialpad");
+                if (mDialpadFragment != null) {
+                    mCallButtonFragment.displayDialpad(false /* show */, false /* animate */);
+                }
             }
+            mShowDialpadRequest = DIALPAD_REQUEST_NONE;
         }
 
         if (mShowPostCharWaitDialogOnResume) {
@@ -328,9 +314,9 @@
     @Override
     protected void onStop() {
         Log.d(this, "onStop()...");
+        enableInCallOrientationEventListener(false);
         InCallPresenter.getInstance().updateIsChangingConfigurations();
         InCallPresenter.getInstance().onActivityStopped();
-        mOrientationEventListener.disable();
         super.onStop();
     }
 
@@ -544,25 +530,6 @@
         return false;
     }
 
-    /**
-     * Handles changes in device rotation.
-     *
-     * @param rotation The new device rotation (one of: {@link Surface#ROTATION_0},
-     * {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180},
-     * {@link Surface#ROTATION_270}).
-     */
-    private void doOrientationChanged(int rotation) {
-        Log.d(this, "doOrientationChanged prevOrientation=" + sPreviousRotation +
-                " newOrientation=" + rotation);
-        // Check to see if the rotation changed to prevent triggering rotation change events
-        // for other configuration changes.
-        if (rotation != sPreviousRotation) {
-            sPreviousRotation = rotation;
-            InCallPresenter.getInstance().onDeviceRotationChange(rotation);
-            InCallPresenter.getInstance().onDeviceOrientationChange(sPreviousRotation);
-        }
-    }
-
     public CallButtonFragment getCallButtonFragment() {
         return mCallButtonFragment;
     }
@@ -657,16 +624,23 @@
             } else if (!newOutgoingCall) {
                 showCallCardFragment(true);
             }
-
             return;
         }
     }
 
+    /**
+     * When relaunching from the dialer app, {@code showDialpad} indicates whether the dialpad
+     * should be shown on launch.
+     *
+     * @param showDialpad {@code true} to indicate the dialpad should be shown on launch, and
+     *                                {@code false} to indicate no change should be made to the
+     *                                dialpad visibility.
+     */
     private void relaunchedFromDialer(boolean showDialpad) {
-        mShowDialpadRequested = showDialpad;
+        mShowDialpadRequest = showDialpad ? DIALPAD_REQUEST_SHOW : DIALPAD_REQUEST_NONE;
         mAnimateDialpadOnShow = true;
 
-        if (mShowDialpadRequested) {
+        if (mShowDialpadRequest == DIALPAD_REQUEST_SHOW) {
             // If there's only one line in use, AND it's on hold, then we're sure the user
             // wants to use the dialpad toward the exact line, so un-hold the holding line.
             final Call call = CallList.getInstance().getActiveOrBackgroundCall();
@@ -941,4 +915,17 @@
     public void setDispatchTouchEventListener(OnTouchListener mDispatchTouchEventListener) {
         this.mDispatchTouchEventListener = mDispatchTouchEventListener;
     }
+
+    /**
+     * Enables the OrientationEventListener if enable flag is true. Disables it if enable is
+     * false
+     * @param enable true or false.
+     */
+    public void enableInCallOrientationEventListener(boolean enable) {
+        if (enable) {
+            mInCallOrientationEventListener.enable(enable);
+        } else {
+            mInCallOrientationEventListener.disable();
+        }
+    }
 }
diff --git a/InCallUI/src/com/android/incallui/InCallOrientationEventListener.java b/InCallUI/src/com/android/incallui/InCallOrientationEventListener.java
new file mode 100644
index 0000000..d3334a3
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/InCallOrientationEventListener.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.incallui;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.view.OrientationEventListener;
+import android.hardware.SensorManager;
+import android.view.Surface;
+import android.content.pm.ActivityInfo;
+
+/**
+ * This class listens to Orientation events and overrides onOrientationChanged which gets
+ * invoked when an orientation change occurs. When that happens, we notify InCallUI registrants
+ * of the change.
+ */
+public class InCallOrientationEventListener extends OrientationEventListener {
+
+    /**
+     * Screen orientation angles one of 0, 90, 180, 270, 360 in degrees.
+     */
+    public static int SCREEN_ORIENTATION_0 = 0;
+    public static int SCREEN_ORIENTATION_90 = 90;
+    public static int SCREEN_ORIENTATION_180 = 180;
+    public static int SCREEN_ORIENTATION_270 = 270;
+    public static int SCREEN_ORIENTATION_360 = 360;
+
+    public static int FULL_SENSOR_SCREEN_ORIENTATION =
+            ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR;
+
+    public static int NO_SENSOR_SCREEN_ORIENTATION =
+            ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+
+    /**
+     * This is to identify dead zones where we won't notify others of orientation changed.
+     * Say for e.g our threshold is x degrees. We will only notify UI when our current rotation is
+     * within x degrees right or left of the screen orientation angles. If it's not within those
+     * ranges, we return SCREEN_ORIENTATION_UNKNOWN and ignore it.
+     */
+    private static int SCREEN_ORIENTATION_UNKNOWN = -1;
+
+    // Rotation threshold is 10 degrees. So if the rotation angle is within 10 degrees of any of
+    // the above angles, we will notify orientation changed.
+    private static int ROTATION_THRESHOLD = 10;
+
+
+    /**
+     * Cache the current rotation of the device.
+     */
+    private static int sCurrentOrientation = SCREEN_ORIENTATION_0;
+
+    public InCallOrientationEventListener(Context context) {
+        super(context);
+    }
+
+    /**
+     * Handles changes in device orientation. Notifies InCallPresenter of orientation changes.
+     *
+     * Note that this API receives sensor rotation in degrees as a param and we convert that to
+     * one of our screen orientation constants - (one of: {@link SCREEN_ORIENTATION_0},
+     * {@link SCREEN_ORIENTATION_90}, {@link SCREEN_ORIENTATION_180},
+     * {@link SCREEN_ORIENTATION_270}).
+     *
+     * @param rotation The new device sensor rotation in degrees
+     */
+    @Override
+    public void onOrientationChanged(int rotation) {
+        if (rotation == OrientationEventListener.ORIENTATION_UNKNOWN) {
+            return;
+        }
+
+        final int orientation = toScreenOrientation(rotation);
+
+        if (orientation != SCREEN_ORIENTATION_UNKNOWN && sCurrentOrientation != orientation) {
+            sCurrentOrientation = orientation;
+            InCallPresenter.getInstance().onDeviceOrientationChange(sCurrentOrientation);
+        }
+    }
+
+    /**
+     * Enables the OrientationEventListener and notifies listeners of current orientation if
+     * notify flag is true
+     * @param notify true or false. Notify device orientation changed if true.
+     */
+    public void enable(boolean notify) {
+        super.enable();
+        if (notify) {
+            InCallPresenter.getInstance().onDeviceOrientationChange(sCurrentOrientation);
+        }
+    }
+
+    /**
+     * Enables the OrientationEventListener with notify flag defaulting to false.
+     */
+    public void enable() {
+        enable(false);
+    }
+
+    /**
+     * Converts sensor rotation in degrees to screen orientation constants.
+     * @param rotation sensor rotation angle in degrees
+     * @return Screen orientation angle in degrees (0, 90, 180, 270). Returns -1 for degrees not
+     * within threshold to identify zones where orientation change should not be trigerred.
+     */
+    private int toScreenOrientation(int rotation) {
+        // Sensor orientation 90 is equivalent to screen orientation 270 and vice versa. This
+        // function returns the screen orientation. So we convert sensor rotation 90 to 270 and
+        // vice versa here.
+        if (isInLeftRange(rotation, SCREEN_ORIENTATION_360, ROTATION_THRESHOLD) ||
+                isInRightRange(rotation, SCREEN_ORIENTATION_0, ROTATION_THRESHOLD)) {
+            return SCREEN_ORIENTATION_0;
+        } else if (isWithinThreshold(rotation, SCREEN_ORIENTATION_90, ROTATION_THRESHOLD)) {
+            return SCREEN_ORIENTATION_270;
+        } else if (isWithinThreshold(rotation, SCREEN_ORIENTATION_180, ROTATION_THRESHOLD)) {
+            return SCREEN_ORIENTATION_180;
+        } else if (isWithinThreshold(rotation, SCREEN_ORIENTATION_270, ROTATION_THRESHOLD)) {
+            return SCREEN_ORIENTATION_90;
+        }
+        return SCREEN_ORIENTATION_UNKNOWN;
+    }
+
+    private static boolean isWithinRange(int value, int begin, int end) {
+        return value >= begin && value < end;
+    }
+
+    private static boolean isWithinThreshold(int value, int center, int threshold) {
+        return isWithinRange(value, center - threshold, center + threshold);
+    }
+
+    private static boolean isInLeftRange(int value, int center, int threshold) {
+        return isWithinRange(value, center - threshold, center);
+    }
+
+    private static boolean isInRightRange(int value, int center, int threshold) {
+        return isWithinRange(value, center, center + threshold);
+    }
+}
diff --git a/InCallUI/src/com/android/incallui/InCallPresenter.java b/InCallUI/src/com/android/incallui/InCallPresenter.java
index 33e4e5a..7d6409c 100644
--- a/InCallUI/src/com/android/incallui/InCallPresenter.java
+++ b/InCallUI/src/com/android/incallui/InCallPresenter.java
@@ -939,17 +939,26 @@
         return mIsActivityPreviouslyStarted;
     }
 
+    /**
+     * Determines if the In-Call app is currently changing configuration.
+     *
+     * @return {@code true} if the In-Call app is changing configuration.
+     */
     public boolean isChangingConfigurations() {
         return mIsChangingConfigurations;
     }
 
+    /**
+     * Tracks whether the In-Call app is currently in the process of changing configuration (i.e.
+     * screen orientation).
+     */
     /*package*/
     void updateIsChangingConfigurations() {
         mIsChangingConfigurations = false;
         if (mInCallActivity != null) {
             mIsChangingConfigurations = mInCallActivity.isChangingConfigurations();
         }
-        Log.d(this, "IsChangingConfigurations=" + mIsChangingConfigurations);
+        Log.v(this, "updateIsChangingConfigurations = " + mIsChangingConfigurations);
     }
 
 
@@ -1132,21 +1141,46 @@
      * @return {@code true} if in-call is now in fullscreen mode.
      */
     public boolean toggleFullscreenMode() {
-        mIsFullScreen = !mIsFullScreen;
-        Log.v(this, "toggleFullscreenMode = " + mIsFullScreen);
-        notifyFullscreenModeChange(mIsFullScreen);
+        boolean isFullScreen = !mIsFullScreen;
+        Log.v(this, "toggleFullscreenMode = " + isFullScreen);
+        setFullScreen(isFullScreen);
         return mIsFullScreen;
     }
 
     /**
+     * Clears the previous fullscreen state.
+     */
+    public void clearFullscreen() {
+        mIsFullScreen = false;
+    }
+
+    /**
      * Changes the fullscreen mode of the in-call UI.
      *
      * @param isFullScreen {@code true} if in-call should be in fullscreen mode, {@code false}
      *                                 otherwise.
      */
     public void setFullScreen(boolean isFullScreen) {
+        setFullScreen(isFullScreen, false /* force */);
+    }
+
+    /**
+     * Changes the fullscreen mode of the in-call UI.
+     *
+     * @param isFullScreen {@code true} if in-call should be in fullscreen mode, {@code false}
+     *                                 otherwise.
+     * @param force {@code true} if fullscreen mode should be set regardless of its current state.
+     */
+    public void setFullScreen(boolean isFullScreen, boolean force) {
         Log.v(this, "setFullScreen = " + isFullScreen);
-        if (mIsFullScreen == isFullScreen) {
+
+        // As a safeguard, ensure we cannot enter fullscreen if the dialpad is shown.
+        if (isDialpadVisible()) {
+            isFullScreen = false;
+            Log.v(this, "setFullScreen overridden as dialpad is shown = " + isFullScreen);
+        }
+
+        if (mIsFullScreen == isFullScreen && !force) {
             Log.v(this, "setFullScreen ignored as already in that state.");
             return;
         }
@@ -1175,6 +1209,21 @@
     }
 
     /**
+     * Called by the {@link CallCardPresenter} to inform of a change in visibility of the secondary
+     * caller info bar.
+     *
+     * @param isVisible {@code true} if the secondary caller info is visible, {@code false}
+     *      otherwise.
+     * @param height the height of the secondary caller info bar.
+     */
+    public void notifySecondaryCallerInfoVisibilityChanged(boolean isVisible, int height) {
+        for (InCallEventListener listener : mInCallEventListeners) {
+            listener.onSecondaryCallerInfoVisibilityChanged(isVisible, height);
+        }
+    }
+
+
+    /**
      * For some disconnected causes, we show a dialog.  This calls into the activity to show
      * the dialog if appropriate for the call.
      */
@@ -1510,64 +1559,32 @@
     }
 
     /**
-     * Handles changes to the device rotation.
+     * Notifies listeners of changes in orientation and notify calls of rotation angle change.
      *
-     * @param rotation The device rotation (one of: {@link Surface#ROTATION_0},
-     *      {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180},
-     *      {@link Surface#ROTATION_270}).
-     */
-    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 (one of: {@link Surface#ROTATION_0},
-     *      {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180},
-     *      {@link Surface#ROTATION_270}).
-     */
-    public static int toRotationAngle(int rotation) {
-        int rotationAngle;
-        switch (rotation) {
-            case Surface.ROTATION_0:
-                rotationAngle = 0;
-                break;
-            case Surface.ROTATION_90:
-                rotationAngle = 90;
-                break;
-            case Surface.ROTATION_180:
-                rotationAngle = 180;
-                break;
-            case Surface.ROTATION_270:
-                rotationAngle = 270;
-                break;
-            default:
-                rotationAngle = 0;
-        }
-        return rotationAngle;
-    }
-
-    /**
-     * Notifies listeners of changes in orientation (e.g. portrait/landscape).
-     *
-     * @param orientation The orientation of the device (one of: {@link Surface#ROTATION_0},
-     *      {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180},
-     *      {@link Surface#ROTATION_270}).
+     * @param orientation The screen orientation of the device (one of:
+     * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_0},
+     * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_90},
+     * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_180},
+     * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_270}).
      */
     public void onDeviceOrientationChange(int orientation) {
+        Log.d(this, "onDeviceOrientationChange: orientation= " + orientation);
+
+        if (mCallList != null) {
+            mCallList.notifyCallsOfDeviceRotation(orientation);
+        } else {
+            Log.w(this, "onDeviceOrientationChange: CallList is null.");
+        }
+
+        // Notify listeners of device orientation changed.
         for (InCallOrientationListener listener : mOrientationListeners) {
             listener.onDeviceOrientationChanged(orientation);
         }
     }
 
     /**
-     * Configures the in-call UI activity so it can change orientations or not.
+     * Configures the in-call UI activity so it can change orientations or not. Enables the
+     * orientation event listener if allowOrientationChange is true, disables it if false.
      *
      * @param allowOrientationChange {@code True} if the in-call UI can change between portrait
      *      and landscape.  {@Code False} if the in-call UI should be locked in portrait.
@@ -1579,10 +1596,15 @@
         }
 
         if (!allowOrientationChange) {
-            mInCallActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR);
+            mInCallActivity.setRequestedOrientation(
+                    InCallOrientationEventListener.NO_SENSOR_SCREEN_ORIENTATION);
         } else {
-            mInCallActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
+            // Using SCREEN_ORIENTATION_FULL_SENSOR allows for reverse-portrait orientation, where
+            // SCREEN_ORIENTATION_SENSOR does not.
+            mInCallActivity.setRequestedOrientation(
+                    InCallOrientationEventListener.FULL_SENSOR_SCREEN_ORIENTATION);
         }
+        mInCallActivity.enableInCallOrientationEventListener(allowOrientationChange);
     }
 
     public void enableScreenTimeout(boolean enable) {
@@ -1639,6 +1661,18 @@
     }
 
     /**
+     * Determines if the dialpad is visible.
+     *
+     * @return {@code true} if the dialpad is visible, {@code false} otherwise.
+     */
+    public boolean isDialpadVisible() {
+        if (mInCallActivity == null) {
+            return false;
+        }
+        return mInCallActivity.isDialpadVisible();
+    }
+
+    /**
      * @return True if the application is currently running in a right-to-left locale.
      */
     public static boolean isRtl() {
@@ -1796,6 +1830,7 @@
      */
     public interface InCallEventListener {
         public void onFullscreenModeChanged(boolean isFullscreenMode);
+        public void onSecondaryCallerInfoVisibilityChanged(boolean isVisible, int height);
     }
 
     public interface InCallUiListener {
diff --git a/InCallUI/src/com/android/incallui/InCallServiceImpl.java b/InCallUI/src/com/android/incallui/InCallServiceImpl.java
index 11202e7..3e4f714 100644
--- a/InCallUI/src/com/android/incallui/InCallServiceImpl.java
+++ b/InCallUI/src/com/android/incallui/InCallServiceImpl.java
@@ -23,6 +23,7 @@
 import android.telecom.CallAudioState;
 import android.telecom.InCallService;
 
+import com.android.dialer.compat.CallAudioStateCompat;
 import com.android.dialer.database.FilteredNumberAsyncQueryHandler;
 
 /**
@@ -33,9 +34,11 @@
  */
 public class InCallServiceImpl extends InCallService {
 
+    // TODO CallAudioState backporting blocked by InCallService backporting
     @Override
     public void onCallAudioStateChanged(CallAudioState audioState) {
-        AudioModeProvider.getInstance().onAudioStateChanged(audioState);
+        AudioModeProvider.getInstance().onAudioStateChanged(audioState.isMuted(),
+                audioState.getRoute(), audioState.getSupportedRouteMask());
     }
 
     @Override
diff --git a/InCallUI/src/com/android/incallui/InCallVideoCallCallback.java b/InCallUI/src/com/android/incallui/InCallVideoCallCallback.java
index 87b886d..76f8c09 100644
--- a/InCallUI/src/com/android/incallui/InCallVideoCallCallback.java
+++ b/InCallUI/src/com/android/incallui/InCallVideoCallCallback.java
@@ -49,11 +49,11 @@
     @Override
     public void onSessionModifyRequestReceived(VideoProfile videoProfile) {
         Log.d(this, " onSessionModifyRequestReceived videoProfile=" + videoProfile);
-        int previousVideoState = CallUtils.getUnPausedVideoState(mCall.getVideoState());
-        int newVideoState = CallUtils.getUnPausedVideoState(videoProfile.getVideoState());
+        int previousVideoState = VideoUtils.getUnPausedVideoState(mCall.getVideoState());
+        int newVideoState = VideoUtils.getUnPausedVideoState(videoProfile.getVideoState());
 
-        boolean wasVideoCall = CallUtils.isVideoCall(previousVideoState);
-        boolean isVideoCall = CallUtils.isVideoCall(newVideoState);
+        boolean wasVideoCall = VideoUtils.isVideoCall(previousVideoState);
+        boolean isVideoCall = VideoUtils.isVideoCall(newVideoState);
 
         // Check for upgrades to video and downgrades to audio.
         if (wasVideoCall && !isVideoCall) {
@@ -97,7 +97,7 @@
         } else if (requestedProfile != null && responseProfile != null) {
             boolean modifySucceeded = requestedProfile.getVideoState() ==
                     responseProfile.getVideoState();
-            boolean isVideoCall = CallUtils.isVideoCall(responseProfile.getVideoState());
+            boolean isVideoCall = VideoUtils.isVideoCall(responseProfile.getVideoState());
             if (modifySucceeded && isVideoCall) {
                 InCallVideoCallCallbackNotifier.getInstance().upgradeToVideoSuccess(mCall);
             } else if (!modifySucceeded && isVideoCall) {
diff --git a/InCallUI/src/com/android/incallui/ProximitySensor.java b/InCallUI/src/com/android/incallui/ProximitySensor.java
index 401ebd1..733a67d 100644
--- a/InCallUI/src/com/android/incallui/ProximitySensor.java
+++ b/InCallUI/src/com/android/incallui/ProximitySensor.java
@@ -16,20 +16,20 @@
 
 package com.android.incallui;
 
+import com.google.common.base.Objects;
+
 import android.content.Context;
 import android.content.res.Configuration;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.os.PowerManager;
-import android.telecom.CallAudioState;
 import android.view.Display;
 
+import com.android.dialer.compat.CallAudioStateCompat;
 import com.android.incallui.AudioModeProvider.AudioModeListener;
 import com.android.incallui.InCallPresenter.InCallState;
 import com.android.incallui.InCallPresenter.InCallStateListener;
 
-import com.google.common.base.Objects;
-
 /**
  * Class manages the proximity sensor for the in-call UI.
  * We enable the proximity sensor while the user in a phone call. The Proximity sensor turns off
@@ -228,9 +228,9 @@
         // turn proximity sensor off and turn screen on immediately if
         // we are using a headset, the keyboard is open, or the device
         // is being held in a horizontal position.
-            boolean screenOnImmediately = (CallAudioState.ROUTE_WIRED_HEADSET == audioMode
-                    || CallAudioState.ROUTE_SPEAKER == audioMode
-                    || CallAudioState.ROUTE_BLUETOOTH == audioMode
+            boolean screenOnImmediately = (CallAudioStateCompat.ROUTE_WIRED_HEADSET == audioMode
+                    || CallAudioStateCompat.ROUTE_SPEAKER == audioMode
+                    || CallAudioStateCompat.ROUTE_BLUETOOTH == audioMode
                     || mIsHardKeyboardOpen);
 
             // We do not keep the screen off when the user is outside in-call screen and we are
@@ -254,7 +254,7 @@
                     .add("offhook", mIsPhoneOffhook ? 1 : 0)
                     .add("hor", horizontal ? 1 : 0)
                     .add("ui", mUiShowing ? 1 : 0)
-                    .add("aud", CallAudioState.audioRouteToString(audioMode))
+                    .add("aud", CallAudioStateCompat.audioRouteToString(audioMode))
                     .toString());
 
             if (mIsPhoneOffhook && !screenOnImmediately) {
diff --git a/InCallUI/src/com/android/incallui/VideoCallFragment.java b/InCallUI/src/com/android/incallui/VideoCallFragment.java
index ce14e48..2c06303 100644
--- a/InCallUI/src/com/android/incallui/VideoCallFragment.java
+++ b/InCallUI/src/com/android/incallui/VideoCallFragment.java
@@ -31,6 +31,7 @@
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 
+import com.android.phone.common.animation.AnimUtils;
 import com.google.common.base.Objects;
 
 /**
@@ -105,6 +106,8 @@
      */
     private boolean mIsLandscape;
 
+    private int mAnimationDuration;
+
     /**
      * 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
@@ -419,6 +422,8 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+
+        mAnimationDuration = getResources().getInteger(R.integer.video_animation_duration);
     }
 
     /**
@@ -454,9 +459,16 @@
      */
     private void centerDisplayView(View displayVideo) {
         if (!mIsLandscape) {
+            ViewGroup.LayoutParams p = displayVideo.getLayoutParams();
+            int height = p.height;
+
             float spaceBesideCallCard = InCallPresenter.getInstance().getSpaceBesideCallCard();
-            float videoViewTranslation = displayVideo.getHeight() / 2
-                    - spaceBesideCallCard / 2;
+            // If space beside call card is zeo, layout hasn't happened yet so there is no point
+            // in attempting to center the view.
+            if (Math.abs(spaceBesideCallCard - 0.0f) < 0.0001) {
+                return;
+            }
+            float videoViewTranslation = height / 2  - spaceBesideCallCard / 2;
             displayVideo.setTranslationY(videoViewTranslation);
         }
     }
@@ -581,6 +593,30 @@
         return mPreviewPhoto;
     }
 
+    /**
+     * Adjusts the location of the video preview view by the specified offset.
+     *
+     * @param shiftUp {@code true} if the preview should shift up, {@code false} if it should shift
+     *      down.
+     * @param offset The offset.
+     */
+    @Override
+    public void adjustPreviewLocation(boolean shiftUp, int offset) {
+        if (sPreviewSurface == null || mPreviewVideoContainer == null) {
+            return;
+        }
+
+        // Set the position of the secondary call info card to its starting location.
+        mPreviewVideoContainer.setTranslationY(shiftUp ? 0 : -offset);
+
+        // Animate the secondary card info slide up/down as it appears and disappears.
+        mPreviewVideoContainer.animate()
+                .setInterpolator(AnimUtils.EASE_OUT_EASE_IN)
+                .setDuration(mAnimationDuration)
+                .translationY(shiftUp ? -offset : 0)
+                .start();
+    }
+
     private void onPresenterChanged(VideoCallPresenter presenter) {
         Log.d(this, "onPresenterChanged: Presenter=" + presenter);
         if (sDisplaySurface != null) {
@@ -668,6 +704,33 @@
         }
     }
 
+    /**
+     * Sets the rotation of the preview surface.  Called when the dimensions change due to a
+     * device orientation change.
+     *
+     * Please note that the screen orientation passed in is subtracted from 360 to get the actual
+     * preview rotation values.
+     *
+     * @param rotation The screen orientation. One of -
+     * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_0},
+     * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_90},
+     * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_180},
+     * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_270}).
+     */
+    @Override
+    public void setPreviewRotation(int orientation) {
+        Log.d(this, "setPreviewRotation: orientation=" + orientation);
+        if (sPreviewSurface != null) {
+            TextureView preview = sPreviewSurface.getTextureView();
+
+            if (preview == null ) {
+                return;
+            }
+
+            preview.setRotation(orientation);
+        }
+    }
+
     @Override
     public void setPreviewSurfaceSize(int width, int height) {
         final boolean isPreviewSurfaceAvailable = sPreviewSurface != null;
@@ -700,7 +763,7 @@
      */
     @Override
     public void setDisplayVideoSize(int width, int height) {
-        Log.d(this, "setDisplayVideoSize: width=" + width + " height=" + height);
+        Log.v(this, "setDisplayVideoSize: width=" + width + " height=" + height);
         if (sDisplaySurface != null) {
             TextureView displayVideo = sDisplaySurface.getTextureView();
             if (displayVideo == null) {
diff --git a/InCallUI/src/com/android/incallui/VideoCallPresenter.java b/InCallUI/src/com/android/incallui/VideoCallPresenter.java
index 84d9c97..621c699 100644
--- a/InCallUI/src/com/android/incallui/VideoCallPresenter.java
+++ b/InCallUI/src/com/android/incallui/VideoCallPresenter.java
@@ -24,7 +24,6 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.provider.ContactsContract;
-import android.telecom.CallAudioState;
 import android.telecom.Connection;
 import android.telecom.InCallService.VideoCall;
 import android.telecom.VideoProfile;
@@ -75,12 +74,14 @@
     public static final boolean DEBUG = false;
 
     /**
-     * Runnable which is posted to schedule automatically entering fullscreen mode.
+     * Runnable which is posted to schedule automatically entering fullscreen mode.  Will not auto
+     * enter fullscreen mode if the dialpad is visible (doing so would make it impossible to exit
+     * the dialpad).
      */
     private Runnable mAutoFullscreenRunnable =  new Runnable() {
         @Override
         public void run() {
-            if (mAutoFullScreenPending) {
+            if (mAutoFullScreenPending && !InCallPresenter.getInstance().isDialpadVisible()) {
                 Log.v(this, "Automatically entering fullscreen mode.");
                 InCallPresenter.getInstance().setFullScreen(true);
                 mAutoFullScreenPending = false;
@@ -153,18 +154,13 @@
     /**
      * Determines the device orientation (portrait/lanscape).
      */
-    private int mDeviceOrientation;
+    private int mDeviceOrientation = InCallOrientationEventListener.SCREEN_ORIENTATION_0;
 
     /**
      * Tracks the state of the preview surface negotiation with the telephony layer.
      */
     private int mPreviewSurfaceState = PreviewSurfaceState.NONE;
 
-    /**
-     * Saves the audio mode which was selected prior to going into a video call.
-     */
-    private static int sPrevVideoAudioMode = AudioModeProvider.AUDIO_MODE_INVALID;
-
     private static boolean mIsVideoMode = false;
 
     /**
@@ -239,6 +235,7 @@
         InCallPresenter.getInstance().addOrientationListener(this);
         // To get updates of video call details changes
         InCallPresenter.getInstance().addDetailsListener(this);
+        InCallPresenter.getInstance().addInCallEventListener(this);
 
         // Register for surface and video events from {@link InCallVideoCallListener}s.
         InCallVideoCallCallbackNotifier.getInstance().addSurfaceChangeListener(this);
@@ -266,6 +263,7 @@
         InCallPresenter.getInstance().removeDetailsListener(this);
         InCallPresenter.getInstance().removeIncomingCallListener(this);
         InCallPresenter.getInstance().removeOrientationListener(this);
+        InCallPresenter.getInstance().removeInCallEventListener(this);
 
         InCallVideoCallCallbackNotifier.getInstance().removeSurfaceChangeListener(this);
         InCallVideoCallCallbackNotifier.getInstance().removeVideoEventListener(this);
@@ -404,8 +402,6 @@
                 " isVideoMode=" + isVideoMode());
 
         if (newState == InCallPresenter.InCallState.NO_CALLS) {
-            updateAudioMode(false);
-
             if (isVideoMode()) {
                 exitVideoMode();
             }
@@ -429,7 +425,7 @@
             // change the camera or UI unless the waiting VT call becomes active.
             primary = callList.getActiveCall();
             currentCall = callList.getIncomingCall();
-            if (!CallUtils.isActiveVideoCall(primary)) {
+            if (!VideoUtils.isActiveVideoCall(primary)) {
                 primary = callList.getIncomingCall();
             }
         } else if (newState == InCallPresenter.InCallState.OUTGOING) {
@@ -469,8 +465,23 @@
         cancelAutoFullScreen();
     }
 
+    /**
+     * Handles changes to the visibility of the secondary caller info bar.
+     *
+     * @param isVisible {@code true} if the secondary caller info is showing, {@code false}
+     *      otherwise.
+     * @param height the height of the secondary caller info bar.
+     */
+    @Override
+    public void onSecondaryCallerInfoVisibilityChanged(boolean isVisible, int height) {
+        Log.d(this,
+                "onSecondaryCallerInfoVisibilityChanged : isVisible = " + isVisible + " height = "
+                        + height);
+        getUi().adjustPreviewLocation(isVisible /* shiftUp */, height);
+    }
+
     private void checkForVideoStateChange(Call call) {
-        final boolean isVideoCall = CallUtils.isVideoCall(call);
+        final boolean isVideoCall = VideoUtils.isVideoCall(call);
         final boolean hasVideoStateChanged = mCurrentVideoState != call.getVideoState();
 
         Log.d(this, "checkForVideoStateChange: isVideoCall= " + isVideoCall
@@ -493,7 +504,7 @@
     }
 
     private void checkForCallStateChange(Call call) {
-        final boolean isVideoCall = CallUtils.isVideoCall(call);
+        final boolean isVideoCall = VideoUtils.isVideoCall(call);
         final boolean hasCallStateChanged = mCurrentCallState != call.getState();
 
         Log.d(this, "checkForCallStateChange: isVideoCall= " + isVideoCall
@@ -512,7 +523,7 @@
             updateCameraSelection(call);
             String newCameraId = cameraManager.getActiveCameraId();
 
-            if (!Objects.equals(prevCameraId, newCameraId) && CallUtils.isActiveVideoCall(call)) {
+            if (!Objects.equals(prevCameraId, newCameraId) && VideoUtils.isActiveVideoCall(call)) {
                 enableCamera(call.getVideoCall(), true);
             }
         }
@@ -531,7 +542,7 @@
     }
 
     private void onPrimaryCallChanged(Call newPrimaryCall) {
-        final boolean isVideoCall = CallUtils.isVideoCall(newPrimaryCall);
+        final boolean isVideoCall = VideoUtils.isVideoCall(newPrimaryCall);
         final boolean isVideoMode = isVideoMode();
 
         Log.d(this, "onPrimaryCallChanged: isVideoCall=" + isVideoCall + " isVideoMode="
@@ -584,7 +595,7 @@
         }
         // 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. ");
+            Log.d(this, " onDetailsChanged: Details not for current active call so returning. ");
             return;
         }
 
@@ -601,7 +612,8 @@
     }
 
     private void checkForOrientationAllowedChange(Call call) {
-        InCallPresenter.getInstance().setInCallAllowsOrientationChange(CallUtils.isVideoCall(call));
+        InCallPresenter.getInstance().setInCallAllowsOrientationChange(
+                VideoUtils.isVideoCall(call));
     }
 
     /**
@@ -640,7 +652,7 @@
             return;
         }
 
-        if (CallUtils.isVideoCall(call) && hasChanged) {
+        if (VideoUtils.isVideoCall(call) && hasChanged) {
             enterVideoMode(call);
         }
     }
@@ -679,56 +691,16 @@
                 videoCall.setDisplaySurface(ui.getDisplayVideoSurface());
             }
 
-            final int rotation = ui.getCurrentRotation();
-            if (rotation != VideoCallFragment.ORIENTATION_UNKNOWN) {
-                videoCall.setDeviceOrientation(InCallPresenter.toRotationAngle(rotation));
-            }
-
+            videoCall.setDeviceOrientation(mDeviceOrientation);
             enableCamera(videoCall, isCameraRequired(newVideoState));
         }
         mCurrentVideoState = newVideoState;
-        updateAudioMode(true);
 
         mIsVideoMode = true;
 
         maybeAutoEnterFullscreen(call);
     }
 
-    //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;
-        }
-
-        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,
-            CallAudioState.ROUTE_BLUETOOTH | CallAudioState.ROUTE_WIRED_HEADSET) &&
-            !isPrevAudioModeValid && enableSpeaker && CallUtils.isVideoCall(mPrimaryCall)) {
-            sPrevVideoAudioMode = currentAudioMode;
-
-            Log.d(this, "Routing audio to speaker");
-            telecomAdapter.setAudioRoute(CallAudioState.ROUTE_SPEAKER);
-        }
-    }
-
     private static boolean isSpeakerEnabledForVideoCalls() {
         // TODO: Make this a carrier configurable setting. For now this is always true. b/20090407
         return true;
@@ -991,23 +963,39 @@
 
     /**
      * Handles changes to the device orientation.
-     *
-     * @param orientation The device orientation (one of: {@link Surface#ROTATION_0},
-     *      {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180},
-     *      {@link Surface#ROTATION_270}).
+     * @param orientation The screen orientation of the device (one of:
+     * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_0},
+     * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_90},
+     * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_180},
+     * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_270}).
      */
     @Override
     public void onDeviceOrientationChanged(int orientation) {
         mDeviceOrientation = orientation;
-        Point previewDimensions = getUi().getPreviewSize();
+
+        VideoCallUi ui = getUi();
+        if (ui == null) {
+            Log.e(this, "onDeviceOrientationChanged: VideoCallUi is null");
+            return;
+        }
+
+        Point previewDimensions = ui.getPreviewSize();
         if (previewDimensions == null) {
             return;
         }
         Log.d(this, "onDeviceOrientationChanged: orientation=" + orientation + " size: "
                 + previewDimensions);
         changePreviewDimensions(previewDimensions.x, previewDimensions.y);
+
+        ui.setPreviewRotation(mDeviceOrientation);
     }
 
+    /**
+     * Handles an incoming upgrade to video request.
+     *
+     * @param call The call the request was received for.
+     * @param videoState The video state that the request wants to upgrade to.
+     */
     @Override
     public void onUpgradeToVideoRequest(Call call, int videoState) {
         Log.d(this, "onUpgradeToVideoRequest call = " + call + " new video state = " + videoState);
@@ -1019,7 +1007,7 @@
             return;
         }
 
-        call.setSessionModificationTo(videoState);
+        call.setRequestedVideoState(videoState);
     }
 
     @Override
@@ -1055,10 +1043,12 @@
 
     /**
      * Sets the preview surface size based on the current device orientation.
+     * See: {@link InCallOrientationEventListener#SCREEN_ORIENTATION_0},
+     * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_90},
+     * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_180},
+     * {@link InCallOrientationEventListener#SCREEN_ORIENTATION_270}).
      *
-     * @param orientation The device orientation (one of: {@link Surface#ROTATION_0},
-     *      {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180},
-     *      {@link Surface#ROTATION_270}).
+     * @param orientation The device orientation
      * @param aspectRatio The aspect ratio of the camera (width / height).
      */
     private void setPreviewSize(int orientation, float aspectRatio) {
@@ -1070,8 +1060,8 @@
         int height;
         int width;
 
-        if (orientation == Surface.ROTATION_90 || orientation == Surface.ROTATION_270) {
-            // Landscape or reverse landscape orientation.
+        if (orientation == InCallOrientationEventListener.SCREEN_ORIENTATION_90 ||
+                orientation == InCallOrientationEventListener.SCREEN_ORIENTATION_270) {
             width = (int) (mMinimumVideoDimension * aspectRatio);
             height = (int) mMinimumVideoDimension;
         } else {
@@ -1089,7 +1079,7 @@
      * @param height peer height
      */
     private void setDisplayVideoSize(int width, int height) {
-        Log.d(this, "setDisplayVideoSize:Received peer width=" + width + " peer height=" + height);
+        Log.v(this, "setDisplayVideoSize: Received peer width=" + width + " height=" + height);
         VideoCallUi ui = getUi();
         if (ui == null) {
             return;
@@ -1097,7 +1087,7 @@
 
         // Get current display size
         Point size = ui.getScreenSize();
-        Log.d("VideoCallPresenter", "setDisplayVideoSize: windowmgr width=" + size.x
+        Log.v(this, "setDisplayVideoSize: windowmgr width=" + size.x
                 + " windowmgr height=" + size.y);
         if (size.y * width > size.x * height) {
             // current display height is too much. Correct it
@@ -1119,7 +1109,7 @@
             return;
         }
 
-        if (!CallUtils.isVideoCall(call) || call.getState() == Call.State.INCOMING) {
+        if (!VideoUtils.isVideoCall(call) || call.getState() == Call.State.INCOMING) {
             InCallPresenter.getInstance().setFullScreen(false);
         }
     }
@@ -1141,7 +1131,7 @@
 
         if (call == null || (
                 call != null && (call.getState() != Call.State.ACTIVE ||
-                        !CallUtils.isVideoCall(call)) ||
+                        !VideoUtils.isVideoCall(call)) ||
                         InCallPresenter.getInstance().isFullscreen())) {
             // Ensure any previously scheduled attempt to enter fullscreen is cancelled.
             cancelAutoFullScreen();
@@ -1189,7 +1179,7 @@
         }
 
         // Clear camera direction if this is not a video call.
-        else if (CallUtils.isAudioCall(call)) {
+        else if (VideoUtils.isAudioCall(call)) {
             cameraDir = Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN;
             call.getVideoSettings().setCameraDir(cameraDir);
         }
@@ -1197,33 +1187,33 @@
         // 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)) {
+        else if (VideoUtils.isVideoCall(activeCall) && VideoUtils.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) ) {
+        else if (VideoUtils.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)) {
+        else if (VideoUtils.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)) {
+        else if (VideoUtils.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)) {
+        else if (VideoUtils.isActiveVideoCall(call)) {
             cameraDir = call.getVideoSettings().getCameraDir();
         }
 
@@ -1248,7 +1238,7 @@
     }
 
     private static boolean isCameraDirectionSet(Call call) {
-        return CallUtils.isVideoCall(call) && call.getVideoSettings().getCameraDir()
+        return VideoUtils.isVideoCall(call) && call.getVideoSettings().getCameraDir()
                     != Call.VideoSettings.CAMERA_DIRECTION_UNKNOWN;
     }
 
@@ -1354,5 +1344,7 @@
         Point getPreviewSize();
         void cleanupSurfaces();
         ImageView getPreviewPhotoView();
+        void adjustPreviewLocation(boolean shiftUp, int offset);
+        void setPreviewRotation(int orientation);
     }
 }
diff --git a/InCallUI/src/com/android/incallui/VideoPauseController.java b/InCallUI/src/com/android/incallui/VideoPauseController.java
index 54e31f8..070448e 100644
--- a/InCallUI/src/com/android/incallui/VideoPauseController.java
+++ b/InCallUI/src/com/android/incallui/VideoPauseController.java
@@ -1,34 +1,21 @@
-/* Copyright (c) 2014, The Linux Foundation. All rights reserved.
+/*
+ * Copyright (C) 2015 The Android Open Source Project
  *
- * 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.
+ * 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
  *
- * 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.
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
  */
 
 package com.android.incallui;
 
-import android.telecom.VideoProfile;
 import com.android.incallui.Call.State;
 import com.android.incallui.InCallPresenter.InCallState;
 import com.android.incallui.InCallPresenter.InCallStateListener;
@@ -42,7 +29,7 @@
  */
 class VideoPauseController implements InCallStateListener, IncomingCallListener,
         SessionModificationListener {
-    private static final String TAG = "VideoPauseController:";
+    private static final String TAG = "VideoPauseController";
 
     /**
      * Keeps track of the current active/foreground call.
@@ -168,7 +155,7 @@
         }
 
         boolean hasPrimaryCallChanged = !areSame(call, mPrimaryCallContext);
-        boolean canVideoPause = CallUtils.canVideoPause(call);
+        boolean canVideoPause = VideoUtils.canVideoPause(call);
         log("onStateChange, hasPrimaryCallChanged=" + hasPrimaryCallChanged);
         log("onStateChange, canVideoPause=" + canVideoPause);
         log("onStateChange, IsInBackground=" + mIsInBackground);
@@ -206,7 +193,7 @@
         log("onPrimaryCallChanged, IsInBackground=" + mIsInBackground);
 
         Preconditions.checkState(!areSame(call, mPrimaryCallContext));
-        final boolean canVideoPause = CallUtils.canVideoPause(call);
+        final boolean canVideoPause = VideoUtils.canVideoPause(call);
 
         if ((isIncomingCall(mPrimaryCallContext) || isDialing(mPrimaryCallContext))
                 && canVideoPause && !mIsInBackground) {
@@ -366,10 +353,10 @@
         if (resume) {
             log("sending resume request, call=" + call);
             call.getVideoCall()
-                    .sendSessionModifyRequest(CallUtils.makeVideoUnPauseProfile(call));
+                    .sendSessionModifyRequest(VideoUtils.makeVideoUnPauseProfile(call));
         } else {
             log("sending pause request, call=" + call);
-            call.getVideoCall().sendSessionModifyRequest(CallUtils.makeVideoPauseProfile(call));
+            call.getVideoCall().sendSessionModifyRequest(VideoUtils.makeVideoPauseProfile(call));
         }
     }
 
@@ -407,7 +394,7 @@
      * @return {@code true} if the call is a video call, {@code false} otherwise.
      */
     private static boolean isVideoCall(CallContext callContext) {
-        return callContext != null && CallUtils.isVideoCall(callContext.getVideoState());
+        return callContext != null && VideoUtils.isVideoCall(callContext.getVideoState());
     }
 
     /**
diff --git a/InCallUI/src/com/android/incallui/VideoUtils.java b/InCallUI/src/com/android/incallui/VideoUtils.java
new file mode 100644
index 0000000..73eb3a9
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/VideoUtils.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.incallui;
+
+import android.telecom.VideoProfile;
+
+import com.android.dialer.compat.DialerCompatUtils;
+
+import com.google.common.base.Preconditions;
+
+public class VideoUtils {
+
+    public static boolean isVideoCall(Call call) {
+        return call != null && isVideoCall(call.getVideoState());
+    }
+
+    public static boolean isVideoCall(int videoState) {
+        if (!DialerCompatUtils.isVideoCompatible()) {
+            return false;
+        }
+
+        return VideoProfile.isTransmissionEnabled(videoState)
+                || VideoProfile.isReceptionEnabled(videoState);
+    }
+
+    public static boolean isBidirectionalVideoCall(Call call) {
+        if (!DialerCompatUtils.isVideoCompatible()) {
+            return false;
+        }
+
+        return VideoProfile.isBidirectional(call.getVideoState());
+    }
+
+    public static boolean isIncomingVideoCall(Call call) {
+        if (!VideoUtils.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 VideoUtils.isVideoCall(call) && call.getState() == Call.State.ACTIVE;
+    }
+
+    public static boolean isOutgoingVideoCall(Call call) {
+        if (!VideoUtils.isVideoCall(call)) {
+            return false;
+        }
+        final int state = call.getState();
+        return Call.State.isDialing(state) || state == Call.State.CONNECTING
+                || state == Call.State.SELECT_PHONE_ACCOUNT;
+    }
+
+    public static boolean isAudioCall(Call call) {
+        if (!DialerCompatUtils.isVideoCompatible()) {
+            return true;
+        }
+
+        return call != null && VideoProfile.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.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.STATE_PAUSED);
+    }
+
+    public static int getPausedVideoState(int videoState) {
+        return videoState | VideoProfile.STATE_PAUSED;
+    }
+
+}