IMS-VT: Add orientation mode and session modification cause notifiers

- Add listeners to listen to call details changed and get the
  orientation mode changes as well as session modification cause
  changes.

- When changes are detected, notify InCall message controller
  which handles the change appropriately

- Add orientation mode and session modification cause resources to
  qtistrings.xml

IMS-VT: Enable orientation listener always for video calls

- start InCallActivity if user accepted call as video from notification.
- Enable orientation listener if user directly accepted video call from noti
- Reset mOrientationMode in OrientationModeHandler once all calls are ended.

IMS_VT: When we set orientation mode compute the correct value and set it

- For video calls, we set the orientation by default to full sensor mode
  and no sensor mode for voice calls. Since DISPLAY_MODE_EVT gives us a
  fixed orientation mode for video calls, compute the correct orientation
  mode in OrientationModeHanlder based on values passed from lower layers.
  Pass this to VideoCallPresenter to set the correct orientation mode
  when we check for changes in video call
- Return ActivityInfo.FULL_SENSOR_SCREEN_ORIENTATION from QtiCallUtils
  toUiOrientationMode for DYNAMIC_CVO mode to support reverse portrait
  matching existing InCallPresenter setRequestedOrientation

 IMS-VT: Optimize handling of orientation mode.

- Invoking setInCallAllowsOrientationChange API triggers
  multiple events which send data across binder. This
  could cause performance degradations in some cases

IMS-VT: Add a primary call tracker to track the current primary call

CRs-Fixed: 993825 846599

Change-Id: I9602a5a0f60c639197936256689407c15715bdcd
diff --git a/InCallUI/res/values/qtistrings.xml b/InCallUI/res/values/qtistrings.xml
index 3c28164..963276e 100644
--- a/InCallUI/res/values/qtistrings.xml
+++ b/InCallUI/res/values/qtistrings.xml
@@ -109,4 +109,28 @@
     <string name="cs_redial_yes">Yes</string>
     <!-- No option of the IMS to CS redial dialog -->
     <string name="cs_redial_no">No</string>
+    <!-- Session modify cause unspecified -->
+    <string name="session_modify_cause_unspecified"></string>
+    <!-- Session modify cause code upgrade local request -->
+    <string name="session_modify_cause_upgrade_local_request">Call upgraded on user request</string>
+    <!-- Session modify cause code upgrade remote request -->
+    <string name="session_modify_cause_upgrade_remote_request">Call upgraded on remote user request</string>
+    <!-- Session modify cause code downgrade local request -->
+    <string name="session_modify_cause_downgrade_local_request">Call downgraded on user request</string>
+    <!-- Session modify cause code downgrade remote request -->
+    <string name="session_modify_cause_downgrade_remote_request">Call downgraded on remote user request</string>
+    <!-- Session modify cause code downgrade rtp timeout -->
+    <string name="session_modify_cause_downgrade_rtp_timeout">Call downgraded due to RTP timeout</string>
+    <!-- Session modify cause code downgrade qos -->
+    <string name="session_modify_cause_downgrade_qos">Call downgraded due to quality of service</string>
+    <!-- Session modify cause code downgrade packet loss -->
+    <string name="session_modify_cause_downgrade_packet_loss">Call downgraded due to packet loss</string>
+    <!-- Session modify cause code downgrade low thrput -->
+    <string name="session_modify_cause_downgrade_low_thrput">Call downgraded due to low throughput</string>
+    <!-- Session modify cause code downgrade thermal mitigation -->
+    <string name="session_modify_cause_downgrade_thermal_mitigation">Call downgraded due to thermal mitigation</string>
+    <!-- Session modify cause code downgrade lipsync -->
+    <string name="session_modify_cause_downgrade_lipsync">Call downgraded due to lipsync</string>
+    <!-- Session modify cause code downgrade generic error -->
+    <string name="session_modify_cause_downgrade_generic_error">Call downgraded due to generic error</string>
 </resources>
diff --git a/InCallUI/src/com/android/incallui/InCallMessageController.java b/InCallUI/src/com/android/incallui/InCallMessageController.java
index d62aae3..b0a7bf7 100644
--- a/InCallUI/src/com/android/incallui/InCallMessageController.java
+++ b/InCallUI/src/com/android/incallui/InCallMessageController.java
@@ -32,10 +32,10 @@
 import android.content.res.Resources;
 import android.os.Bundle;
 
-import org.codeaurora.ims.QtiCallConstants;
 import com.android.incallui.InCallPresenter.InCallDetailsListener;
 import com.android.incallui.InCallVideoCallCallbackNotifier.VideoEventListener;
-
+import com.android.incallui.InCallVideoCallCallbackNotifier.SessionModificationListener;
+import org.codeaurora.ims.QtiCallConstants;
 
 /**
  * This class listens to incoming events for the listener classes it implements. It should
@@ -45,9 +45,12 @@
  * {@class CallSubstateNotifier} notifies it through the onCallSubstateChanged callback.
  */
 public class InCallMessageController implements InCallSubstateListener, VideoEventListener,
-        CallList.Listener {
+        CallList.Listener, InCallSessionModificationCauseListener {
 
     private static InCallMessageController sInCallMessageController;
+
+    private PrimaryCallTracker mPrimaryCallTracker;
+
     private Context mContext;
 
     /**
@@ -57,25 +60,33 @@
     }
 
     /**
-     * Handles set up of the {@class InCallSubstateListener}. Instantiates the context needed by
-     * the class and adds a listener to listen to call substate changes.
+     * Handles set up of the {@class InCallMessageController}. Instantiates the context needed by
+     * the class and adds a listener to listen to call substate changes, video event changes,
+     * session modification cause changes, call state changes.
      */
     public void setUp(Context context) {
         mContext = context;
+        mPrimaryCallTracker = new PrimaryCallTracker();
         CallSubstateNotifier.getInstance().addListener(this);
         InCallVideoCallCallbackNotifier.getInstance().addVideoEventListener(this);
         CallList.getInstance().addListener(this);
+        SessionModificationCauseNotifier.getInstance().addListener(this);
+        InCallPresenter.getInstance().addListener(mPrimaryCallTracker);
     }
 
     /**
-     * Handles tear down of the {@class InCallSubstateListener}. Sets the context to null and
-     * unregisters it's call substate listener.
+     * Handles tear down of the {@class InCallMessageController}. Sets the context to null and
+     * unregisters it's call substate,  video event, session modification cause, call state
+     * listeners.
      */
     public void tearDown() {
         mContext = null;
         CallSubstateNotifier.getInstance().removeListener(this);
         InCallVideoCallCallbackNotifier.getInstance().removeVideoEventListener(this);
         CallList.getInstance().removeListener(this);
+        SessionModificationCauseNotifier.getInstance().removeListener(this);
+        InCallPresenter.getInstance().removeListener(mPrimaryCallTracker);
+        mPrimaryCallTracker = null;
     }
 
     /**
@@ -97,8 +108,8 @@
         Log.d(this, "onCallSubstateChanged - Call : " + call + " call substate changed to " +
                 callSubstate);
 
-        if (mContext == null) {
-            Log.e(this, "onCallSubstateChanged - Context is null. Return");
+        if (mContext == null || !mPrimaryCallTracker.isPrimaryCall(call)) {
+            Log.e(this, "onCallSubstateChanged - Context is null/not primary call.");
             return;
         }
 
@@ -141,11 +152,11 @@
      */
     @Override
     public void onVideoQualityChanged(final Call call, final int videoQuality) {
-        Log.d(this, "Call : " + call + " onVideoQualityChanged. Video quality changed to " +
+        Log.d(this, "onVideoQualityChanged: - Call : " + call + " Video quality changed to " +
                 videoQuality);
 
-        if (mContext == null) {
-            Log.e(this, "onVideoQualityChanged - Context is null. Return");
+        if (mContext == null || !mPrimaryCallTracker.isPrimaryCall(call)) {
+            Log.e(this, "onVideoQualityChanged - Context is null/not primary call.");
             return;
         }
         final Resources resources = mContext.getResources();
@@ -166,7 +177,7 @@
         Log.d(this, "onCallSessionEvent: event = " + event);
 
         if (mContext == null) {
-            Log.e(this, "onCallSessionEvent - Context is null. Return");
+            Log.e(this, "onCallSessionEvent - Context is null.");
             return;
         }
         final Resources resources = mContext.getResources();
@@ -258,4 +269,59 @@
              break;
        }
     }
+
+    /*
+     * Handles any session modifictaion cause changes in the call.
+     *
+     * @param call The call for which orientation mode changed.
+     * @param sessionModificationCause The new session modifictaion cause
+     */
+    @Override
+    public void onSessionModificationCauseChanged(Call call, int sessionModificationCause) {
+        Log.d(this, "onSessionModificationCauseChanged: Call : " + call +
+                " Call modified due to "  + sessionModificationCause);
+
+        if (mContext == null || !mPrimaryCallTracker.isPrimaryCall(call)) {
+            Log.e(this,
+                    "onSessionModificationCauseChanged- Context is null/not primary call.");
+            return;
+        }
+
+        QtiCallUtils.displayToast(mContext,
+                getSessionModificationCauseResourceId(sessionModificationCause));
+    }
+
+    /**
+     * This method returns the string resource id (i.e. display string) that corresponds to the
+     * session modification cause code.
+     */
+    private static int getSessionModificationCauseResourceId(int cause) {
+        switch(cause) {
+            case QtiCallConstants.CAUSE_CODE_UNSPECIFIED:
+                return R.string.session_modify_cause_unspecified;
+            case QtiCallConstants.CAUSE_CODE_SESSION_MODIFY_UPGRADE_LOCAL_REQ:
+                return R.string.session_modify_cause_upgrade_local_request;
+            case QtiCallConstants.CAUSE_CODE_SESSION_MODIFY_UPGRADE_REMOTE_REQ:
+                return R.string.session_modify_cause_upgrade_remote_request;
+            case QtiCallConstants.CAUSE_CODE_SESSION_MODIFY_DOWNGRADE_LOCAL_REQ:
+                return R.string.session_modify_cause_downgrade_local_request;
+            case QtiCallConstants.CAUSE_CODE_SESSION_MODIFY_DOWNGRADE_REMOTE_REQ:
+                return R.string.session_modify_cause_downgrade_remote_request;
+            case QtiCallConstants.CAUSE_CODE_SESSION_MODIFY_DOWNGRADE_RTP_TIMEOUT:
+                return R.string.session_modify_cause_downgrade_rtp_timeout;
+            case QtiCallConstants.CAUSE_CODE_SESSION_MODIFY_DOWNGRADE_QOS:
+                return R.string.session_modify_cause_downgrade_qos;
+            case QtiCallConstants.CAUSE_CODE_SESSION_MODIFY_DOWNGRADE_PACKET_LOSS:
+                return R.string.session_modify_cause_downgrade_packet_loss;
+            case QtiCallConstants.CAUSE_CODE_SESSION_MODIFY_DOWNGRADE_LOW_THRPUT:
+                return R.string.session_modify_cause_downgrade_low_thrput;
+            case QtiCallConstants.CAUSE_CODE_SESSION_MODIFY_DOWNGRADE_THERM_MITIGATION:
+                return R.string.session_modify_cause_downgrade_thermal_mitigation;
+            case QtiCallConstants.CAUSE_CODE_SESSION_MODIFY_DOWNGRADE_LIPSYNC:
+                return R.string.session_modify_cause_downgrade_lipsync;
+            case QtiCallConstants.CAUSE_CODE_SESSION_MODIFY_DOWNGRADE_GENERIC_ERROR:
+            default:
+                return R.string.session_modify_cause_downgrade_generic_error;
+        }
+    }
 }
diff --git a/InCallUI/src/com/android/incallui/InCallPresenter.java b/InCallUI/src/com/android/incallui/InCallPresenter.java
index 9142a8a..14f143a 100644
--- a/InCallUI/src/com/android/incallui/InCallPresenter.java
+++ b/InCallUI/src/com/android/incallui/InCallPresenter.java
@@ -23,6 +23,7 @@
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
+
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Point;
@@ -349,7 +350,9 @@
         mCallList.setFilteredNumberQueryHandler(mFilteredQueryHandler);
 
         InCallMessageController.getInstance().setUp(mContext);
+        OrientationModeHandler.getInstance().setUp();
         addDetailsListener(CallSubstateNotifier.getInstance());
+        addDetailsListener(SessionModificationCauseNotifier.getInstance());
 
         InCallZoomController.getInstance().setUp(mContext);
         Log.d(this, "Finished InCallPresenter.setUp");
@@ -375,9 +378,11 @@
         InCallUiStateNotifier.getInstance().tearDown();
         InCallVideoCallCallbackNotifier.getInstance().removeSessionModificationListener(this);
         InCallMessageController.getInstance().tearDown();
+        OrientationModeHandler.getInstance().tearDown();
         removeDetailsListener(CallSubstateNotifier.getInstance());
 
         InCallZoomController.getInstance().tearDown();
+        removeDetailsListener(SessionModificationCauseNotifier.getInstance());
     }
 
     private void attemptFinishActivity() {
@@ -920,6 +925,9 @@
         Call call = mCallList.getIncomingCall();
         if (call != null) {
             TelecomAdapter.getInstance().answerCall(call.getId(), videoState);
+            if (VideoUtils.isVideoCall(videoState)) {
+                showInCall(false, false/* newOutgoingCall */);
+            }
         }
     }
 
@@ -1752,25 +1760,17 @@
      * 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.
+     * @param orientation {@link ActivityInfo#screenOrientation} Actual orientation value to set
      */
-    public void setInCallAllowsOrientationChange(boolean allowOrientationChange) {
+    public void setInCallAllowsOrientationChange(int orientation) {
         if (mInCallActivity == null) {
             Log.e(this, "InCallActivity is null. Can't set requested orientation.");
             return;
         }
 
-        if (!allowOrientationChange) {
-            mInCallActivity.setRequestedOrientation(
-                    InCallOrientationEventListener.NO_SENSOR_SCREEN_ORIENTATION);
-        } else {
-            // 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);
+        mInCallActivity.setRequestedOrientation(orientation);
+        mInCallActivity.enableInCallOrientationEventListener(
+                orientation == InCallOrientationEventListener.FULL_SENSOR_SCREEN_ORIENTATION);
     }
 
     public void enableScreenTimeout(boolean enable) {
diff --git a/InCallUI/src/com/android/incallui/InCallSessionModificationCauseListener.java b/InCallUI/src/com/android/incallui/InCallSessionModificationCauseListener.java
new file mode 100644
index 0000000..e6ad761
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/InCallSessionModificationCauseListener.java
@@ -0,0 +1,37 @@
+/* Copyright (c) 2015, 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;
+
+/**
+ * This interface will be implemented by classes that wish to listen to session modification cause
+ * updates.
+ */
+public interface InCallSessionModificationCauseListener {
+    public void onSessionModificationCauseChanged(Call call, int sessionModificationCause);
+}
diff --git a/InCallUI/src/com/android/incallui/OrientationModeHandler.java b/InCallUI/src/com/android/incallui/OrientationModeHandler.java
new file mode 100644
index 0000000..bbb1a6c
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/OrientationModeHandler.java
@@ -0,0 +1,189 @@
+/* Copyright (c) 2015, 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.content.pm.ActivityInfo;
+import android.os.Bundle;
+import com.android.incallui.InCallPresenter.InCallDetailsListener;
+import com.android.incallui.InCallPresenter.InCallUiListener;
+import org.codeaurora.ims.QtiCallConstants;
+
+/**
+ * This class listens to incoming events from the {@class InCallDetailsListener}.
+ * When call details change, this class is notified and we parse the extras from the details to
+ * figure out if orientation mode has changed and if changed, we call setRequestedOrientation
+ * on the activity to set the orientation mode for the device.
+ *
+ */
+public class OrientationModeHandler implements InCallDetailsListener, InCallUiListener {
+
+    private static OrientationModeHandler sOrientationModeHandler;
+
+    private PrimaryCallTracker mPrimaryCallTracker;
+
+    private int mOrientationMode = QtiCallConstants.ORIENTATION_MODE_UNSPECIFIED;
+
+    /**
+     * Returns a singleton instance of {@class OrientationModeHandler}
+     */
+    public static synchronized OrientationModeHandler getInstance() {
+        if (sOrientationModeHandler == null) {
+            sOrientationModeHandler = new OrientationModeHandler();
+        }
+        return sOrientationModeHandler;
+    }
+
+    /**
+     * Private constructor. Must use getInstance() to get this singleton.
+     */
+    private OrientationModeHandler() {
+    }
+
+    /**
+     * Handles set up of the {@class OrientationModeHandler}. Registers primary call tracker to
+     * listen to call state changes and registers this class to listen to call details changes.
+     */
+    public void setUp() {
+        mPrimaryCallTracker = new PrimaryCallTracker();
+        InCallPresenter.getInstance().addListener(mPrimaryCallTracker);
+        InCallPresenter.getInstance().addDetailsListener(this);
+        InCallPresenter.getInstance().addInCallUiListener(this);
+    }
+
+    /**
+     * Handles tear down of the {@class OrientationModeHandler}. Unregisters primary call tracker
+     * from listening to call state changes and unregisters this class from listening to call
+     * details changes.
+     */
+    public void tearDown() {
+        InCallPresenter.getInstance().removeListener(mPrimaryCallTracker);
+        InCallPresenter.getInstance().removeDetailsListener(this);
+        InCallPresenter.getInstance().removeInCallUiListener(this);
+        mOrientationMode = QtiCallConstants.ORIENTATION_MODE_UNSPECIFIED;
+        mPrimaryCallTracker = null;
+    }
+
+    /**
+     * Overrides onDetailsChanged method of {@class InCallDetailsListener}. We are
+     * notified when call details change and extract the orientation mode from the
+     * extras, detect if the mode has changed and set the orientation mode for the device.
+     */
+    @Override
+    public void onDetailsChanged(Call call, android.telecom.Call.Details details) {
+        Log.d(this, "onDetailsChanged: - call: " + call + "details: " + details);
+        mayBeUpdateOrientationMode(call, details);
+    }
+
+    /**
+     * This API conveys if incall experience is showing or not.
+     *
+     * @param showing TRUE if incall experience is showing else FALSE
+     */
+    @Override
+    public void onUiShowing(boolean showing) {
+        Call call = mPrimaryCallTracker.getPrimaryCall();
+        Log.d(this, "onUiShowing showing: " + showing + " call = " + call);
+
+        if (!showing || call == null) {
+            return;
+        }
+
+        mayBeUpdateOrientationMode(call, call.getTelecomCall().getDetails());
+    }
+
+    private void mayBeUpdateOrientationMode(Call call, android.telecom.Call.Details details) {
+        final Bundle extras =  (call != null && details != null) ? details.getExtras() : null;
+        final int orientationMode = (extras != null) ? extras.getInt(
+                QtiCallConstants.ORIENTATION_MODE_EXTRA_KEY,
+                QtiCallConstants.ORIENTATION_MODE_UNSPECIFIED) :
+                QtiCallConstants.ORIENTATION_MODE_UNSPECIFIED;
+
+        Log.d(this, "mayBeUpdateOrientationMode : orientationMode: " + orientationMode +
+                " mOrientationMode : " + mOrientationMode);
+        if (InCallPresenter.getInstance().getActivity() == null) {
+            Log.w(this, "mayBeUpdateOrientationMode : InCallActivity is null");
+            return;
+        }
+
+        if (orientationMode != mOrientationMode && orientationMode !=
+                QtiCallConstants.ORIENTATION_MODE_UNSPECIFIED) {
+            mOrientationMode = orientationMode;
+            onOrientationModeChanged(call, mOrientationMode);
+        }
+    }
+
+    /**
+     * Handles any orientation mode changes in the call.
+     *
+     * @param call The call for which orientation mode changed.
+     * @param orientationMode The new orientation mode of the device
+     */
+    private void onOrientationModeChanged(Call call, int orientationMode) {
+        Log.d(this, "onOrientationModeChanged: Call : " + call + " orientation mode = " +
+                orientationMode);
+
+        if (!mPrimaryCallTracker.isPrimaryCall(call)) {
+            Log.e(this, "Can't set requested orientation on a non-primary call");
+            return;
+        }
+        final InCallActivity inCallActivity = InCallPresenter.getInstance().getActivity();
+        if (inCallActivity != null) {
+            inCallActivity.setRequestedOrientation(QtiCallUtils.toUiOrientationMode(
+                    orientationMode));
+        }
+    }
+
+    /**
+     * Returns the current orientation mode based on the receipt of DISPLAY_MODE_EVT from lower
+     * layers and whether the call is a video call or not. If we have a video call and we
+     * did receive a valid orientation mode, return the corresponding
+     * {@link ActivityInfo#ScreenOrientation} else return
+     * ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR. If we are in a voice call, return
+     * ActivityInfo.SCREEN_ORIENTATION_NOSENSOR.
+     *
+     * @param call The current call.
+     */
+    public int getOrientation(Call call) {
+        if (VideoUtils.isVideoCall(call)) {
+            return (mOrientationMode == QtiCallConstants.ORIENTATION_MODE_UNSPECIFIED) ?
+                    ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR :
+                    QtiCallUtils.toUiOrientationMode(mOrientationMode);
+        } else {
+            return ActivityInfo.SCREEN_ORIENTATION_NOSENSOR;
+        }
+    }
+
+    /**
+     * Returns the current orientation mode.
+     * @see #getOrientation(Call)
+     */
+    public int getCurrentOrientation() {
+        return QtiCallUtils.toUiOrientationMode(mOrientationMode);
+    }
+}
diff --git a/InCallUI/src/com/android/incallui/PrimaryCallTracker.java b/InCallUI/src/com/android/incallui/PrimaryCallTracker.java
new file mode 100644
index 0000000..4212bbc
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/PrimaryCallTracker.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ * Not a Contribution.
+ *
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.incallui;
+
+import com.android.incallui.InCallPresenter.InCallStateListener;
+import com.android.incallui.InCallPresenter.IncomingCallListener;
+import java.util.Objects;
+
+/**
+ * Listens to call state changes from {@class InCallStateListener} and keeps track of the current
+ * primary call.
+ */
+public class PrimaryCallTracker implements InCallStateListener, IncomingCallListener {
+
+    private Call mPrimaryCall;
+
+    public PrimaryCallTracker() {
+    }
+
+    @Override
+    public void onIncomingCall(InCallPresenter.InCallState oldState,
+            InCallPresenter.InCallState newState, Call call) {
+        // same logic should happen as with onStateChange()
+        onStateChange(oldState, InCallPresenter.InCallState.INCOMING, CallList.getInstance());
+    }
+
+    /**
+     * Handles state changes (including incoming calls)
+     *
+     * @param newState The in call state.
+     * @param callList The call list.
+     */
+    @Override
+    public void onStateChange(InCallPresenter.InCallState oldState,
+            InCallPresenter.InCallState newState, CallList callList) {
+        Log.d(this, "onStateChange: oldState" + oldState + " newState=" + newState +
+                "callList =" + callList);
+
+        // Determine the primary active call.
+        Call primaryCall = null;
+
+        if (newState == InCallPresenter.InCallState.INCOMING) {
+            primaryCall = callList.getIncomingCall();
+        } else if (newState == InCallPresenter.InCallState.OUTGOING) {
+            primaryCall = callList.getOutgoingCall();
+        } else if (newState == InCallPresenter.InCallState.PENDING_OUTGOING) {
+            primaryCall = callList.getPendingOutgoingCall();
+        } else if (newState == InCallPresenter.InCallState.INCALL) {
+            primaryCall = callList.getActiveCall();
+        } else {
+            primaryCall = callList.getBackgroundCall();
+        }
+
+        if (!Objects.equals(mPrimaryCall, primaryCall)) {
+            mPrimaryCall = primaryCall;
+        }
+    }
+
+    /**
+     * Returns the current primary call.
+     */
+    public Call getPrimaryCall() {
+        return mPrimaryCall;
+    }
+
+    /**
+     * Checks if the current call passed in is a primary call. Returns true if it is, false
+     * otherwise
+     *
+     * @param Call The call to be compared with the primary call.
+     */
+    public boolean isPrimaryCall(final Call call) {
+        return Objects.equals(mPrimaryCall, call);
+    }
+}
diff --git a/InCallUI/src/com/android/incallui/QtiCallUtils.java b/InCallUI/src/com/android/incallui/QtiCallUtils.java
index e741e67..f6016b6 100644
--- a/InCallUI/src/com/android/incallui/QtiCallUtils.java
+++ b/InCallUI/src/com/android/incallui/QtiCallUtils.java
@@ -41,6 +41,7 @@
 import android.content.DialogInterface;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.content.pm.ActivityInfo;
 import android.telecom.InCallService.VideoCall;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -49,6 +50,7 @@
 import java.util.ArrayList;
 
 import org.codeaurora.internal.IExtTelephony;
+import org.codeaurora.ims.QtiCallConstants;
 
 /**
  * This class contains Qti specific utiltity functions.
@@ -420,4 +422,21 @@
         }
         return (dsdaEnabled == null) ? false : dsdaEnabled;
     }
+
+    /**
+     * This method converts the QtiCallConstants' Orientation modes to the ActivityInfo
+     * screen orientation mode.
+     */
+    public static int toUiOrientationMode(int orientationMode) {
+        switch(orientationMode) {
+            case QtiCallConstants.ORIENTATION_MODE_LANDSCAPE:
+                return ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+            case QtiCallConstants.ORIENTATION_MODE_PORTRAIT:
+                return ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+            case QtiCallConstants.ORIENTATION_MODE_DYNAMIC:
+                return ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR;
+            default:
+                return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+        }
+    }
 }
diff --git a/InCallUI/src/com/android/incallui/SessionModificationCauseNotifier.java b/InCallUI/src/com/android/incallui/SessionModificationCauseNotifier.java
new file mode 100644
index 0000000..034e029
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/SessionModificationCauseNotifier.java
@@ -0,0 +1,112 @@
+/* Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *     * Redistributions of source code must retain the above copyright
+ *       notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ *       copyright notice, this list of conditions and the following
+ *       disclaimer in the documentation and/or other materials provided
+ *       with the distribution.
+ *     * Neither the name of The Linux Foundation nor the names of its
+ *       contributors may be used to endorse or promote products derived
+ *       from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+ * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+ * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+package com.android.incallui;
+
+import android.os.Bundle;
+import com.android.incallui.InCallPresenter.InCallDetailsListener;
+import com.google.common.base.Preconditions;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.List;
+
+import org.codeaurora.ims.QtiCallConstants;
+
+/**
+ * This class listens to incoming events from the {@class InCallDetailsListener}.
+ * When call details change, this class is notified and we parse the extras from the details to
+ * figure out if session modification cause has been sent when a call upgrades/downgrades and
+ * notify the {@class InCallMessageController} to display the indication on UI.
+ */
+public class SessionModificationCauseNotifier implements InCallDetailsListener{
+
+    private final List<InCallSessionModificationCauseListener> mSessionModificationCauseListeners
+            = new CopyOnWriteArrayList<>();
+
+    private static SessionModificationCauseNotifier sSessionModificationCauseNotifier;
+
+    /**
+     * Returns a singleton instance of {@class SessionModificationCauseNotifier}
+     */
+    public static synchronized SessionModificationCauseNotifier getInstance() {
+        if (sSessionModificationCauseNotifier == null) {
+            sSessionModificationCauseNotifier = new SessionModificationCauseNotifier();
+        }
+        return sSessionModificationCauseNotifier;
+    }
+
+    /**
+     * Adds a new session modification cause listener. Users interested in this cause
+     * should add a listener of type {@class InCallSessionModificationCauseListener}
+     */
+    public void addListener(InCallSessionModificationCauseListener listener) {
+        Preconditions.checkNotNull(listener);
+        mSessionModificationCauseListeners.add(listener);
+    }
+
+    /**
+     * Removes an existing session modification cause listener. Users listening to any cause
+     * changes when not interested any more can de-register an existing listener of type
+     * {@class InCallSessionModificationCauseListener}
+     */
+    public void removeListener(InCallSessionModificationCauseListener listener) {
+        if (listener != null) {
+            mSessionModificationCauseListeners.remove(listener);
+        } else {
+            Log.e(this, "Can't remove null listener");
+        }
+    }
+
+    /**
+     * Private constructor. Must use getInstance() to get this singleton.
+     */
+    private SessionModificationCauseNotifier() {
+    }
+
+    /**
+     * Overrides onDetailsChanged method of {@class InCallDetailsListener}. We are
+     * notified when call details change and extract the session modification cause from the
+     * extras, detect if the cause has changed and notify all registered listeners.
+     */
+    @Override
+    public void onDetailsChanged(Call call, android.telecom.Call.Details details) {
+        Log.d(this, "onDetailsChanged: - call: " + call + "details: " + details);
+        final Bundle extras =  (call != null && details != null) ? details.getExtras() : null;
+        final int sessionModificationCause = (extras != null) ? extras.getInt(
+                QtiCallConstants.SESSION_MODIFICATION_CAUSE_EXTRA_KEY,
+                QtiCallConstants.CAUSE_CODE_UNSPECIFIED) :
+                QtiCallConstants.CAUSE_CODE_UNSPECIFIED;
+
+        if (sessionModificationCause != QtiCallConstants.CAUSE_CODE_UNSPECIFIED) {
+            Preconditions.checkNotNull(mSessionModificationCauseListeners);
+            for (InCallSessionModificationCauseListener listener :
+                    mSessionModificationCauseListeners) {
+                listener.onSessionModificationCauseChanged(call, sessionModificationCause);
+            }
+        }
+    }
+}
diff --git a/InCallUI/src/com/android/incallui/VideoCallPresenter.java b/InCallUI/src/com/android/incallui/VideoCallPresenter.java
index df4060e..7d71078 100644
--- a/InCallUI/src/com/android/incallui/VideoCallPresenter.java
+++ b/InCallUI/src/com/android/incallui/VideoCallPresenter.java
@@ -574,6 +574,7 @@
         } else if (isVideoCall) {
             Log.d(this, "onPrimaryCallChanged: Entering video mode...");
 
+            checkForOrientationAllowedChange(newPrimaryCall);
             updateCameraSelection(newPrimaryCall);
             enterVideoMode(newPrimaryCall);
         }
@@ -631,8 +632,12 @@
     }
 
     private void checkForOrientationAllowedChange(Call call) {
-        InCallPresenter.getInstance().setInCallAllowsOrientationChange(
-                VideoUtils.isVideoCall(call));
+        final int currMode = OrientationModeHandler.getInstance().getCurrentOrientation();
+        final int newMode = OrientationModeHandler.getInstance().getOrientation(call);
+
+        if (newMode != currMode) {
+            InCallPresenter.getInstance().setInCallAllowsOrientationChange(newMode);
+        }
     }
 
     /**