Merge "Fix long geographical location shows incomplete" into atel.lnx.2.0-dev
diff --git a/InCallUI/res/layout/call_button_fragment.xml b/InCallUI/res/layout/call_button_fragment.xml
index 9ed4e56..3bc306e 100644
--- a/InCallUI/res/layout/call_button_fragment.xml
+++ b/InCallUI/res/layout/call_button_fragment.xml
@@ -158,6 +158,13 @@
             android:contentDescription="@string/onscreenAddParticipant"
             android:visibility="gone" />
 
+        <!-- "Hide Me" -->
+        <ImageButton android:id="@+id/hideMe"
+            style="@style/InCallButton"
+            android:background="@drawable/btn_compound_video_off"
+            android:contentDescription="@string/qti_ims_hideMeText_unselected"
+            android:visibility="gone" />
+
         <!-- "Blind transfer" -->
         <ImageButton android:id="@+id/blindTransfer"
             style="@style/InCallButton"
diff --git a/InCallUI/res/values/qtistrings.xml b/InCallUI/res/values/qtistrings.xml
index 5876c87..18873cc 100644
--- a/InCallUI/res/values/qtistrings.xml
+++ b/InCallUI/res/values/qtistrings.xml
@@ -81,6 +81,10 @@
     <!-- Modify call error cause -->
     <string name="modify_call_failed_due_to_low_battery">Modify call failed due to low battery.</string>
 
+    <string name="qti_ims_hideMeText_unselected">Hide Me</string>
+    <string name="qti_ims_hideMeText_selected">Show Me</string>
+    <string name="qti_ims_defaultImage_fallback">Using default image</string>
+
     <!-- Description of the call transfer related strings [CHAR LIMIT=NONE] -->
     <string name="qti_ims_transfer_num_error">Number not set. Provide the number via IMS settings and retry.</string>
     <string name="qti_ims_transfer_request_error">Call Transfer request had failed.</string>
diff --git a/InCallUI/src/com/android/incallui/CallButtonFragment.java b/InCallUI/src/com/android/incallui/CallButtonFragment.java
index fa76bba..bd76096 100644
--- a/InCallUI/src/com/android/incallui/CallButtonFragment.java
+++ b/InCallUI/src/com/android/incallui/CallButtonFragment.java
@@ -37,6 +37,7 @@
 import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_RX_VIDEO_CALL;
 import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_VO_VIDEO_CALL;
 import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_ADD_PARTICIPANT;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_HIDE_ME;
 
 import android.app.Activity;
 import android.content.ActivityNotFoundException;
@@ -116,7 +117,8 @@
         public static final int BUTTON_RX_VIDEO_CALL = 17;
         public static final int BUTTON_VO_VIDEO_CALL = 18;
         public static final int BUTTON_ADD_PARTICIPANT = 19;
-        public static final int BUTTON_COUNT = 20;
+        public static final int BUTTON_HIDE_ME = 20;
+        public static final int BUTTON_COUNT = 21;
     }
 
     private SparseIntArray mButtonVisibilityMap = new SparseIntArray(BUTTON_COUNT);
@@ -138,6 +140,7 @@
     private ImageButton mAssuredTransferButton;
     private ImageButton mConsultativeTransferButton;
     private ImageButton mAddParticipantButton;
+    private ImageButton mHideMeButton;
     private ImageButton mRecordButton;
     private ImageButton mRxTxVideoCallButton;
     private ImageButton mRxVideoCallButton;
@@ -213,6 +216,8 @@
         mConsultativeTransferButton.setOnClickListener(this);
         mAddParticipantButton = (ImageButton) parent.findViewById(R.id.addParticipant);
         mAddParticipantButton.setOnClickListener(this);
+        mHideMeButton = (ImageButton) parent.findViewById(R.id.hideMe);
+        mHideMeButton.setOnClickListener(this);
         mOverflowButton = (ImageButton) parent.findViewById(R.id.overflowButton);
         mOverflowButton.setOnClickListener(this);
         mManageVideoCallConferenceButton = (ImageButton) parent.findViewById(
@@ -269,6 +274,8 @@
             getPresenter().showDialpadClicked(!mShowDialpadButton.isSelected());
         } else if (id == R.id.addParticipant) {
             getPresenter().addParticipantClicked();
+        } else if (id == R.id.hideMe) {
+            getPresenter().hideMeClicked(!mHideMeButton.isSelected());
         } else if (id == R.id.changeToVideoButton) {
             getPresenter().changeToVideoClicked();
         } else if (id == R.id.changeToVoiceButton) {
@@ -400,6 +407,7 @@
                 mChangeToVoiceButton,
                 mAddCallButton,
                 mMergeButton,
+                mHideMeButton,
                 mBlindTransferButton,
                 mAssuredTransferButton,
                 mConsultativeTransferButton,
@@ -502,6 +510,7 @@
         mOverflowButton.setEnabled(isEnabled);
         mManageVideoCallConferenceButton.setEnabled(isEnabled);
         mAddParticipantButton.setEnabled(isEnabled);
+        mHideMeButton.setEnabled(isEnabled);
         mRecordButton.setEnabled(isEnabled);
         mRxTxVideoCallButton.setEnabled(isEnabled);
         mRxVideoCallButton.setEnabled(isEnabled);
@@ -562,6 +571,8 @@
             return mRxVideoCallButton;
         } else if (id == BUTTON_VO_VIDEO_CALL) {
             return mVoVideoCallButton;
+        } else if (id == BUTTON_HIDE_ME) {
+            return mHideMeButton;
         } else {
             Log.w(this, "Invalid button id");
             return null;
@@ -604,6 +615,16 @@
         }
     }
 
+    @Override
+    public void setHideMe(boolean value) {
+        if (mHideMeButton.isSelected() != value) {
+            mHideMeButton.setSelected(value);
+            mHideMeButton.setContentDescription(getContext().getString(
+                    value ? R.string.qti_ims_hideMeText_selected
+                            : R.string.qti_ims_hideMeText_unselected));
+        }
+    }
+
     private void addToOverflowMenu(int id, View button, PopupMenu menu) {
         button.setVisibility(View.GONE);
         menu.getMenu().add(Menu.NONE, id, Menu.NONE, button.getContentDescription());
diff --git a/InCallUI/src/com/android/incallui/CallButtonPresenter.java b/InCallUI/src/com/android/incallui/CallButtonPresenter.java
index e71b3ab..f4451c3 100644
--- a/InCallUI/src/com/android/incallui/CallButtonPresenter.java
+++ b/InCallUI/src/com/android/incallui/CallButtonPresenter.java
@@ -35,6 +35,7 @@
 import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_RX_VIDEO_CALL;
 import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_VO_VIDEO_CALL;
 import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_ADD_PARTICIPANT;
+import static com.android.incallui.CallButtonFragment.Buttons.BUTTON_HIDE_ME;
 
 import android.content.Context;
 import android.os.Build;
@@ -75,6 +76,8 @@
     private boolean mPreviousMuteState = false;
     private static final int MAX_PARTICIPANTS_LIMIT = 6;
     private boolean mEnhanceEnable = false;
+    // Holds TRUE if user clicked on "hideme" option else holds false
+    private static boolean sIsHideMe = false;
 
     // NOTE: Capability constant definition has been duplicated to avoid bundling
     // the Dialer with Frameworks.
@@ -145,6 +148,9 @@
             mCall = callList.getIncomingCall();
         } else {
             mCall = null;
+            if (newState == InCallState.NO_CALLS) {
+                sIsHideMe = false;
+            }
         }
         updateUi(newState, mCall);
     }
@@ -399,6 +405,22 @@
         }
     }
 
+    /**
+     * Handles click on hide me button
+     * @param isHideMe True if user selected hide me option else false
+     */
+    public void hideMeClicked(boolean isHideMe) {
+        sIsHideMe = isHideMe;
+
+        if (getUi() != null && mCall != null) {
+            updateButtonsState(mCall);
+        }
+
+        /* Click on hideme shall change the static image state i.e. decision
+           is made in VideoCallPresenter whether to replace preview video with
+           static image or whether to resume preview video streaming */
+        InCallPresenter.getInstance().notifyStaticImageStateChanged(isHideMe);
+    }
 
     /**
      * Stop or start client's video transmission.
@@ -537,7 +559,13 @@
         ui.showButton(BUTTON_ADD_CALL, showAddCall);
         ui.showButton(BUTTON_UPGRADE_TO_VIDEO, showUpgradeToVideo && !mEnhanceEnable);
         ui.showButton(BUTTON_DOWNGRADE_TO_AUDIO, showDowngradeToAudio && !useExt);
-        ui.showButton(BUTTON_SWITCH_CAMERA, isVideo);
+        Log.v(this, "updateButtonsState sIsHideMe: " + sIsHideMe);
+        ui.setHideMe(sIsHideMe);
+        // show switch camera button only if video call is NOT in hideme mode
+        ui.showButton(BUTTON_SWITCH_CAMERA, isVideo && !sIsHideMe);
+        // show hide me button only for active video calls
+        ui.showButton(BUTTON_HIDE_ME, isCallActive && isVideo &&
+                QtiCallUtils.shallTransmitStaticImage(getUi().getContext()));
         ui.showButton(BUTTON_PAUSE_VIDEO, isVideo && !useExt && !useCustomVideoUi &&
                 !mEnhanceEnable);
         if (isVideo) {
@@ -627,6 +655,7 @@
         void setEnabled(boolean on);
         void setMute(boolean on);
         void setHold(boolean on);
+        void setHideMe(boolean on);
         void setCameraSwitched(boolean isBackFacingCamera);
         void setVideoPaused(boolean isPaused);
         void setAudio(int mode);
diff --git a/InCallUI/src/com/android/incallui/CallCardPresenter.java b/InCallUI/src/com/android/incallui/CallCardPresenter.java
index 4317a5e..63d98f4 100644
--- a/InCallUI/src/com/android/incallui/CallCardPresenter.java
+++ b/InCallUI/src/com/android/incallui/CallCardPresenter.java
@@ -258,7 +258,11 @@
 
         if (mInCallContactInteractions != null &&
                 (oldState == InCallState.INCOMING || newState == InCallState.INCOMING)) {
-            ui.showContactContext(newState != InCallState.INCOMING);
+            boolean showIncomingVideo = (primary != null) ? VideoCallPresenter.showIncomingVideo(
+                    primary.getVideoState(), primary.getState()) : false;
+            if (!showIncomingVideo) {
+                ui.showContactContext(newState != InCallState.INCOMING);
+            }
         }
 
         Log.d(this, "Primary call: " + primary);
@@ -562,7 +566,10 @@
             ui.setPrimaryCallElapsedTime(false, 0);
             mCallTimer.cancel();
         } else {
-            ui.setPrimaryCallElapsedTime(true, mPrimary.getCallDuration());
+            final long connectTime = mPrimary.getConnectTimeMillis();
+            if (connectTime > 0) {
+                ui.setPrimaryCallElapsedTime(true, mPrimary.getCallDuration());
+            }
         }
     }
 
@@ -660,6 +667,8 @@
     }
 
     private void updateContactInteractions() {
+        boolean showIncomingVideo = (mPrimary != null) ? VideoCallPresenter.showIncomingVideo(
+                mPrimary.getVideoState(), mPrimary.getState()) : false;
         if (mPrimary != null && mPrimaryContactInfo != null
                 && (mPrimaryContactInfo.locationAddress != null
                         || mPrimaryContactInfo.openingHours != null)) {
@@ -675,9 +684,13 @@
                     mDistanceHelper.calculateDistance(mPrimaryContactInfo.locationAddress),
                     mPrimaryContactInfo.openingHours);
             getUi().setContactContextContent(mInCallContactInteractions.getListAdapter());
-            getUi().showContactContext(mPrimary.getState() != State.INCOMING);
+            if (!showIncomingVideo) {
+                getUi().showContactContext(mPrimary.getState() != State.INCOMING);
+            }
         } else {
-            getUi().showContactContext(false);
+            if (!showIncomingVideo) {
+                getUi().showContactContext(false);
+            }
         }
     }
 
@@ -943,6 +956,10 @@
             return;
         }
 
+        final boolean notUpdateSecondary = mSecondary.getState() == Call.State.ACTIVE
+                && !mSecondary.can(android.telecom.Call.Details.CAPABILITY_SUPPORT_HOLD)
+                && !mSecondary.can(android.telecom.Call.Details.CAPABILITY_HOLD);
+        Log.d(TAG, "notUpdateSecondary:" + notUpdateSecondary);
         if (mSecondary.isConferenceCall()) {
             ui.setSecondary(
                     true /* show */,
@@ -953,7 +970,7 @@
                     true /* isConference */,
                     mSecondary.isVideoCall(mContext),
                     mIsFullscreen);
-        } else if (mSecondaryContactInfo != null) {
+        } else if (mSecondaryContactInfo != null && !notUpdateSecondary) {
             Log.d(TAG, "updateSecondaryDisplayInfo() " + mSecondaryContactInfo);
             String name = getNameForCall(mSecondaryContactInfo);
             boolean nameIsNumber = name != null && name.equals(mSecondaryContactInfo.number);
@@ -1169,6 +1186,11 @@
         updatePrimaryDisplayInfo();
     }
 
+    @Override
+    public void onSendStaticImageStateChanged(boolean isEnabled) {
+        //No-op
+    }
+
     private boolean isPrimaryCallActive() {
         return mPrimary != null && mPrimary.getState() == Call.State.ACTIVE;
     }
diff --git a/InCallUI/src/com/android/incallui/InCallOrientationEventListener.java b/InCallUI/src/com/android/incallui/InCallOrientationEventListener.java
index 3cab6dc..ed10b73 100644
--- a/InCallUI/src/com/android/incallui/InCallOrientationEventListener.java
+++ b/InCallUI/src/com/android/incallui/InCallOrientationEventListener.java
@@ -20,7 +20,9 @@
 import android.content.res.Configuration;
 import android.view.OrientationEventListener;
 import android.hardware.SensorManager;
+import android.view.Display;
 import android.view.Surface;
+import android.view.WindowManager;
 import android.content.pm.ActivityInfo;
 
 /**
@@ -51,7 +53,7 @@
      * 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;
+    public 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.
@@ -64,8 +66,11 @@
     private static int sCurrentOrientation = SCREEN_ORIENTATION_0;
     private boolean mEnabled = false;
 
+    private static WindowManager sWindowManager = null;
+
     public InCallOrientationEventListener(Context context) {
         super(context);
+        sWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
     }
 
     /**
@@ -175,4 +180,32 @@
     private static boolean isInRightRange(int value, int center, int threshold) {
         return isWithinRange(value, center, center + threshold);
     }
+
+    /**
+     * Returns the current display orientation.
+     * return values 0, 90, 180 and 270
+     * -1 if unknown
+     */
+    public static int getCurrentUiOrientation() {
+        if (sWindowManager == null) return SCREEN_ORIENTATION_UNKNOWN;
+        Display display = sWindowManager.getDefaultDisplay();
+        if (display == null) return SCREEN_ORIENTATION_UNKNOWN;
+        int rotation = display.getRotation();
+        int orientation = SCREEN_ORIENTATION_UNKNOWN;
+        switch (rotation) {
+            case Surface.ROTATION_0:
+                orientation = SCREEN_ORIENTATION_0;
+                break;
+            case Surface.ROTATION_90:
+                orientation = SCREEN_ORIENTATION_90;
+                break;
+            case Surface.ROTATION_180:
+                orientation = SCREEN_ORIENTATION_180;
+                break;
+            case Surface.ROTATION_270:
+                orientation = SCREEN_ORIENTATION_270;
+                break;
+        }
+        return orientation;
+    }
 }
diff --git a/InCallUI/src/com/android/incallui/InCallPresenter.java b/InCallUI/src/com/android/incallui/InCallPresenter.java
index bbf7ff6..5a5d0cb 100644
--- a/InCallUI/src/com/android/incallui/InCallPresenter.java
+++ b/InCallUI/src/com/android/incallui/InCallPresenter.java
@@ -745,11 +745,7 @@
         maybeShowErrorDialogOnDisconnect(call);
 
         // Send broadcast to dismiss deflect dialog.
-        if (Objects.equals(call.getId(), QtiCallUtils.getDeflectOrTransferCallId())) {
-            Intent intent = new Intent(QtiCallUtils.INTENT_ACTION_DIALOG_DISMISS);
-            mInCallActivity.sendBroadcast(intent);
-            QtiCallUtils.setDeflectOrTransferCallId(null);
-        }
+        dismissDeflectOrTransferDialog(call.getId());
 
         // We need to do the run the same code as onCallListChange.
         onCallListChange(mCallList);
@@ -771,6 +767,8 @@
         if (call == null) {
             return;
         }
+        // Send broadcast to dismiss deflect dialog.
+        dismissDeflectOrTransferDialog(call.getId());
 
         wakeUpScreen();
         call.setRequestedVideoState(videoState);
@@ -782,6 +780,19 @@
     }
 
     /**
+     * Sends broadcast to dismiss Deflection or ECT dialog
+     */
+    private void dismissDeflectOrTransferDialog(String callId) {
+        Log.d(this, "dismissDeflectOrTransferDialog() callId = " + callId);
+        if (Objects.equals(callId, QtiCallUtils.getDeflectOrTransferCallId())) {
+            Intent intent = new Intent(QtiCallUtils.INTENT_ACTION_DIALOG_DISMISS);
+            mInCallActivity.sendBroadcast(intent);
+            QtiCallUtils.setDeflectOrTransferCallId(null);
+        }
+    }
+
+
+    /**
      * Given the call list, return the state in which the in-call screen should be.
      */
     public InCallState getPotentialStateFromCallList(CallList callList) {
@@ -1384,6 +1395,17 @@
         }
     }
 
+   /**
+     * Called by the {@link CallButtonPresenter} to inform of a change in hide me selection.
+     *
+     * @param isEnabled {@code True} if entering hide me mode.
+     */
+    public void notifyStaticImageStateChanged(boolean isEnabled) {
+        for (InCallEventListener listener : mInCallEventListeners) {
+            listener.onSendStaticImageStateChanged(isEnabled);
+        }
+    }
+
     /**
      * Update  color of sim card icon
      */
@@ -1699,6 +1721,9 @@
             if (mExternalCallNotifier != null && mExternalCallList != null) {
                 mExternalCallList.removeExternalCallListener(mExternalCallNotifier);
             }
+            mExternalCallNotifier = null;
+            mExternalCallList = null;
+
             mStatusBarNotifier = null;
 
             InCallCsRedialHandler.getInstance().tearDown();
@@ -2137,6 +2162,7 @@
         public void onSecondaryCallerInfoVisibilityChanged(boolean isVisible, int height);
         public void updatePrimaryCallState();
         public void onIncomingVideoAvailabilityChanged(boolean isAvailable);
+        public void onSendStaticImageStateChanged(boolean isEnabled);
     }
 
     public interface InCallUiListener {
diff --git a/InCallUI/src/com/android/incallui/QtiCallUtils.java b/InCallUI/src/com/android/incallui/QtiCallUtils.java
index 0c85a0b..6f99b2b 100644
--- a/InCallUI/src/com/android/incallui/QtiCallUtils.java
+++ b/InCallUI/src/com/android/incallui/QtiCallUtils.java
@@ -264,6 +264,18 @@
     }
 
     /**
+     * Checks the boolean flag in config file to figure out if transmitting static image
+     * in a video call is enabled or not
+     */
+    public static boolean shallTransmitStaticImage(Context context) {
+        if (context == null) {
+            Log.w(context, "Context is null...");
+        }
+        return context != null && QtiImsExtUtils.shallTransmitStaticImage(context);
+    }
+
+
+    /**
      * Checks the boolean flag in config file to figure out if it support preview before the accept
      * video call or not
      */
diff --git a/InCallUI/src/com/android/incallui/VideoCallFragment.java b/InCallUI/src/com/android/incallui/VideoCallFragment.java
index 3a708ab..fe1422f 100644
--- a/InCallUI/src/com/android/incallui/VideoCallFragment.java
+++ b/InCallUI/src/com/android/incallui/VideoCallFragment.java
@@ -34,6 +34,9 @@
 import android.view.WindowManager;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
+import android.content.pm.PackageManager;
+import android.Manifest;
+import android.os.Process;
 
 import com.android.dialer.R;
 import com.android.phone.common.animation.AnimUtils;
@@ -66,6 +69,7 @@
      * Used to indicate that the UI rotation is unknown.
      */
     public static final int ORIENTATION_UNKNOWN = -1;
+    private static final int PERMISSION_REQUEST_READ_EXTERNAL_STORAGE = 1;
 
     // Static storage used to retain the video surfaces across Activity restart.
     // TextureViews are not parcelable, so it is not possible to store them in the saved state.
@@ -446,6 +450,46 @@
     }
 
     /**
+      * Callback received when a permissions request has been completed.
+      *
+      * @param requestCode The request code passed in requestPermissions API
+      * @param permissions The requested permissions. Never null.
+      * @param grantResults The grant results for the corresponding permissions
+      *     which is either PackageManager#PERMISSION_GRANTED}
+      *     or PackageManager#PERMISSION_DENIED}. Never null.
+      */
+    @Override
+    public void onRequestPermissionsResult(int requestCode, String[] permissions,
+            int[] grantResults) {
+        switch (requestCode) {
+            case PERMISSION_REQUEST_READ_EXTERNAL_STORAGE: {
+                // If request is cancelled, the result arrays are empty.
+                getPresenter().onReadStoragePermissionResponse(grantResults.length > 0 &&
+                        grantResults[0] == PackageManager.PERMISSION_GRANTED);
+                return;
+            }
+            default:
+                Log.w(TAG, "onRequestPermissionsResult: Unhandled requestCode = " + requestCode);
+        }
+    }
+
+    public void onRequestReadStoragePermission() {
+        final Activity activity = getActivity();
+        if (activity == null) {
+            Log.e(this, "onRequestReadStoragePermission: Activity is null");
+            return;
+        }
+        if ((activity.checkSelfPermission(
+                Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)) {
+            requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
+                    PERMISSION_REQUEST_READ_EXTERNAL_STORAGE);
+        } else {
+            // permission already granted
+            getPresenter().onReadStoragePermissionResponse(true);
+        }
+    }
+
+    /**
      * Handles creation of the activity and initialization of the presenter.
      *
      * @param savedInstanceState The saved instance state.
@@ -689,6 +733,18 @@
         return sPreviewSurface == null ? null : sPreviewSurface.getSurface();
     }
 
+    @Override
+    public Point getPreviewContainerSize() {
+        if (mPreviewVideoContainer == null) {
+            return null;
+        }
+        FrameLayout.LayoutParams params = (FrameLayout.LayoutParams)
+                mPreviewVideoContainer.getLayoutParams();
+        Log.d(this, "getPreviewContainerSize: width = " + params.width +
+                " height = " + params.height);
+        return new Point(params.width, params.height);
+    }
+
     /**
      * Changes the dimensions of the preview surface.  Called when the dimensions change due to a
      * device orientation change.
@@ -724,13 +780,6 @@
                 }
                 mPreviewVideoContainer.setLayoutParams(containerParams);
             }
-
-            // The width and height are interchanged outside of this method based on the current
-            // orientation, so we can transform using "width", which will be either the width or
-            // the height.
-            Matrix transform = new Matrix();
-            transform.setScale(-1, 1, width/2, 0);
-            preview.setTransform(transform);
         }
     }
 
@@ -757,7 +806,10 @@
                 return;
             }
 
-            preview.setRotation(orientation);
+            // Set transform matrix based on orientation
+            ViewGroup.LayoutParams params = preview.getLayoutParams();
+            setTransformMatrixForRotation(preview, orientation,
+                    params.width, params.height);
         }
     }
 
@@ -913,6 +965,56 @@
     }
 
     /**
+     * Sets the transform matrix to textureview based on orientation so that image
+     * is always up right.
+     */
+    private void setTransformMatrixForRotation(TextureView textureView, int rotation,
+            int width, int height) {
+
+        Matrix matrix = new Matrix();
+
+        if (rotation != InCallOrientationEventListener.SCREEN_ORIENTATION_0) {
+
+            float[] srcArray =  new float[] {
+                    0.f, 0.f, // top left
+                    width, 0.f, // top right
+                    0.f, height, // bottom left
+                    width, height, // bottom right
+            };
+
+            float[] destArray = srcArray;
+
+            // Rotate the image for landscape device orientations
+            if (rotation == InCallOrientationEventListener.SCREEN_ORIENTATION_90) {
+                destArray = new float[] {
+                        0.f, height, // top left
+                        0.f, 0.f, // top right
+                        width, height, // bottom left
+                        width, 0.f, // bottom right
+                };
+            } else if (rotation == InCallOrientationEventListener.SCREEN_ORIENTATION_270) {
+                destArray = new float[] {
+                        width, 0.f, // top left
+                        width, height, // top right
+                        0.f, 0.f, // bottom left
+                        0.f, height, // bottom right
+                };
+            } else if (rotation == InCallOrientationEventListener.SCREEN_ORIENTATION_180) {
+                // Flip the image vertically and horizontally for reverse portrait
+                destArray = new float[] {
+                        width, height, // top left
+                        0.f, height, // top right
+                        width, 0.f, // bottom left
+                        0.f, 0.f, // bottom right
+                };
+            }
+
+            matrix.setPolyToPoly(srcArray, 0, destArray, 0, 4);
+        }
+        textureView.setTransform(matrix);
+    }
+
+    /**
      * Resizes a surface so that it has the same size as the full screen and so that it is
      * centered vertically below the call card.
      *
diff --git a/InCallUI/src/com/android/incallui/VideoCallPresenter.java b/InCallUI/src/com/android/incallui/VideoCallPresenter.java
index 58405a8..df05a34 100644
--- a/InCallUI/src/com/android/incallui/VideoCallPresenter.java
+++ b/InCallUI/src/com/android/incallui/VideoCallPresenter.java
@@ -16,8 +16,13 @@
 
 package com.android.incallui;
 
+import android.content.ContentResolver;
 import android.content.Context;
 import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
 import android.graphics.Point;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -33,6 +38,7 @@
 import android.view.Surface;
 import android.widget.ImageView;
 
+import org.codeaurora.ims.QtiImsException;
 import org.codeaurora.ims.utils.QtiImsExtUtils;
 
 import com.android.contacts.common.ContactPhotoManager;
@@ -169,6 +175,10 @@
     private int mPreviewSurfaceState = PreviewSurfaceState.NONE;
 
     private static boolean mIsVideoMode = false;
+    // Holds TRUE if default image should be used as static image else holds FALSE
+    private static boolean sUseDefaultImage = false;
+    // Holds TRUE if static image needs to be transmitted instead of video preview stream
+    private static boolean sShallTransmitStaticImage = false;
 
     /**
      * Contact photo manager to retrieve cached contact photo information.
@@ -274,6 +284,13 @@
             return;
         }
 
+        // Initialize current ui rotation
+        final int uiOrientation = InCallOrientationEventListener.getCurrentUiOrientation();
+        Log.d(this, "onUiReady: uiOrientation = " + uiOrientation);
+        if (uiOrientation != InCallOrientationEventListener.SCREEN_ORIENTATION_UNKNOWN) {
+            mDeviceOrientation = uiOrientation;
+        }
+
         // Register for call state changes last
         InCallPresenter.getInstance().addListener(this);
         InCallPresenter.getInstance().addDetailsListener(this);
@@ -449,7 +466,10 @@
             case VideoCallFragment.SURFACE_PREVIEW:
                 if (mPictureModeHelper.canShowPreviewVideoView() &&
                         mPictureModeHelper.canShowIncomingVideoView()) {
-                    InCallZoomController.getInstance().onPreviewSurfaceClicked(mVideoCall);
+                    if (!shallTransmitStaticImage()) {
+                        // show zoom bar only when UE is showing video stream in preview
+                        InCallZoomController.getInstance().onPreviewSurfaceClicked(mVideoCall);
+                    }
                 } else {
                     isFullscreen = InCallPresenter.getInstance().toggleFullscreenMode();
                     Log.d(this, "toggleFullScreen = " + isFullscreen + "surfaceId =" + surfaceId);
@@ -490,6 +510,8 @@
             if (isVideoMode()) {
                 exitVideoMode();
             }
+            sShallTransmitStaticImage = false;
+            sUseDefaultImage = false;
             cleanupSurfaces();
         }
 
@@ -659,7 +681,6 @@
             updateCameraSelection(newPrimaryCall);
             adjustVideoMode(newPrimaryCall);
         }
-        checkForOrientationAllowedChange(newPrimaryCall);
     }
 
     private boolean isVideoMode() {
@@ -765,7 +786,8 @@
 
     private boolean isCameraRequired(int videoState) {
         return ((VideoProfile.isBidirectional(videoState) ||
-                VideoProfile.isTransmissionEnabled(videoState)) && !mIsInBackground);
+                VideoProfile.isTransmissionEnabled(videoState)) && !mIsInBackground &&
+                !shallTransmitStaticImage());
     }
 
     private boolean isCameraRequired() {
@@ -853,6 +875,175 @@
         mIsVideoMode = false;
     }
 
+    private boolean shallTransmitStaticImage() {
+        return sShallTransmitStaticImage;
+    }
+
+    private void setPreviewImage(Drawable previewDrawable) {
+        final VideoCallUi ui = getUi();
+        if (ui == null || previewDrawable == null) {
+            Log.e(this, "setPreviewImage: ui/previewDrawable is null");
+            return;
+        }
+
+        ImageView photoView = ui.getPreviewPhotoView();
+        if (photoView == null) {
+            Log.e(this, "setPreviewImage: previewView is null");
+            return;
+        }
+        photoView.setScaleType(ImageView.ScaleType.CENTER_CROP);
+        photoView.setImageDrawable(previewDrawable);
+    }
+
+    private void setPreviewImage(Bitmap previewBitmap) {
+        final VideoCallUi ui = getUi();
+        if (ui == null || previewBitmap == null) {
+            Log.e(this, "setPreviewImage: ui/previewBitmap is null");
+            return;
+        }
+
+        ImageView photoView = ui.getPreviewPhotoView();
+        if (photoView == null) {
+            Log.e(this, "setPreviewImage: previewView is null");
+            return;
+        }
+        photoView.setImageBitmap(previewBitmap);
+    }
+
+    private void setPauseImage() {
+        String uriStr = null;
+        Uri uri = null;
+
+        if (!QtiCallUtils.shallTransmitStaticImage(mContext) || mVideoCall == null) {
+            return;
+        }
+
+        if (shallTransmitStaticImage()) {
+            uriStr = sUseDefaultImage ? "" :
+                    QtiImsExtUtils.getStaticImageUriStr(mContext.getContentResolver());
+        }
+
+        uri = uriStr != null ? Uri.parse(uriStr) : null;
+        Log.d(this, "setPauseImage parsed uri = " + uri + " sUseDefaultImage = "
+                + sUseDefaultImage);
+        mVideoCall.setPauseImage(uri);
+    }
+
+    private Drawable getDefaultImage() {
+       return mContext.getResources().
+                    getDrawable(R.drawable.img_no_image_automirrored);
+    }
+
+    public void maybeLoadPreConfiguredImageAsync() {
+        Log.d(this, "maybeLoadPreConfiguredImageAsync: shallTransmitStaticImage = "
+                + shallTransmitStaticImage() + " sUseDefaultImage = " + sUseDefaultImage);
+
+        if (!shallTransmitStaticImage()) {
+            return;
+        }
+
+        if (sUseDefaultImage) {
+            /* no need to display toast message here since user would've been
+               already altered of default image usage */
+            setPreviewImage(getDefaultImage());
+            setPauseImage();
+            return;
+        }
+
+        final AsyncTask<Void, Void, Bitmap> task = new AsyncTask<Void, Void, Bitmap>() {
+            // Decode image in background.
+            @Override
+            protected Bitmap doInBackground(Void... params) {
+                try {
+                    return loadImage();
+                } catch (QtiImsException ex) {
+                    Log.e(this, "loadImage: ex = " + ex);
+                }
+                return null;
+            }
+
+            // Once complete, set bitmap/pause image.
+            @Override
+            protected void onPostExecute(Bitmap bitmap) {
+                sUseDefaultImage = bitmap == null;
+                if (sUseDefaultImage) {
+                    QtiCallUtils.displayToast(mContext, R.string.qti_ims_defaultImage_fallback);
+                    setPreviewImage(getDefaultImage());
+                } else {
+                    setPreviewImage(bitmap);
+                }
+                setPauseImage();
+            }
+        };
+        task.execute();
+    }
+
+    public void onReadStoragePermissionResponse(boolean isGranted) {
+        Log.d(this,"onReadStoragePermissionResponse: granted = " + isGranted);
+
+        // Use default image when permissions are not granted
+        sUseDefaultImage = !isGranted;
+        if (!isGranted) {
+            QtiCallUtils.displayToast(mContext, R.string.qti_ims_defaultImage_fallback);
+        }
+        showVideoUi(mCurrentVideoState, mCurrentCallState, isConfCall());
+    }
+
+    private Bitmap loadImage() throws QtiImsException {
+        final VideoCallUi ui = getUi();
+        if (ui == null) {
+            Log.w(this, "loadImage: ui is null");
+            return null;
+        }
+
+        Point previewDimensions = ui.getPreviewContainerSize();
+        if (previewDimensions == null) {
+            Log.w(this, "loadImage: previewDimensions is null");
+            return null;
+        }
+
+        Log.d(this, "loadImage: size: " + previewDimensions);
+        return QtiImsExtUtils.getStaticImage(mContext, previewDimensions.x,
+                previewDimensions.y);
+    }
+
+    /**
+     * Handles a change to the video call hide me selection
+     *
+     * @param shallTransmitStaticImage {@code true} if the app should show static image in preview,
+     * {@code false} otherwise.
+     */
+    @Override
+    public void onSendStaticImageStateChanged(boolean shallTransmitStaticImage) {
+
+        Log.d(this, "onSendStaticImageStateChanged: shallTransmitStaticImage: "
+                + shallTransmitStaticImage);
+
+        final VideoCallUi ui = getUi();
+        sShallTransmitStaticImage = shallTransmitStaticImage;
+
+        if (mPrimaryCall == null || !VideoUtils.isActiveVideoCall(mPrimaryCall)) {
+            Log.w(this, "onSendStaticImageStateChanged: received for non-active video call");
+            return;
+        }
+
+        if (mVideoCall == null || ui == null) {
+            Log.w(this, "onSendStaticImageStateChanged: VideoCall/VideoCallUi is null");
+            return;
+        }
+
+        enableCamera(mVideoCall, isCameraRequired(mCurrentVideoState));
+        if (shallTransmitStaticImage) {
+            // Handle showing static image in preview based on external storage permissions
+            ui.onRequestReadStoragePermission();
+        } else {
+            /* When not required to transmit static image, update video ui visibility
+               to reflect streaming video in preview */
+            showVideoUi(mCurrentVideoState, mCurrentCallState, isConfCall());
+            mVideoCall.setPauseImage(null);
+        }
+    }
+
     /**
      * Based on the current video state and call state, show or hide the incoming and
      * outgoing video surfaces.  The outgoing video surface is shown any time video is transmitting.
@@ -880,9 +1071,9 @@
                 mPictureModeHelper.canShowPreviewVideoView();
 
         Log.v(this, "showVideoUi : showIncoming = " + showIncomingVideo + " showOutgoing = "
-                + showOutgoingVideo);
+                + showOutgoingVideo + " shallTransmitStaticImage = " + shallTransmitStaticImage());
         if (showIncomingVideo || showOutgoingVideo) {
-            ui.showVideoViews(showOutgoingVideo, showIncomingVideo);
+            ui.showVideoViews(showOutgoingVideo && !shallTransmitStaticImage(), showIncomingVideo);
 
             boolean hidePreview = shallHidePreview(isConf, videoState);
             Log.v(this, "showVideoUi, hidePreview = " + hidePreview);
@@ -892,9 +1083,10 @@
 
             if (showOutgoingVideo) {
                 setPreviewSize(mDeviceOrientation, mPreviewAspectRatio);
+                maybeLoadPreConfiguredImageAsync();
             }
 
-            if (isVideoReceptionEnabled) {
+            if (isVideoReceptionEnabled && !shallTransmitStaticImage()) {
                 loadProfilePhotoAsync();
             }
         } else {
@@ -1055,6 +1247,11 @@
 
         mPreviewSurfaceState = PreviewSurfaceState.CAPABILITIES_RECEIVED;
         changePreviewDimensions(width, height);
+        ui.setPreviewRotation(mDeviceOrientation);
+
+        if (shallTransmitStaticImage()) {
+            setPauseImage();
+        }
 
         // Check if the preview surface is ready yet; if it is, set it on the {@code VideoCall}.
         // If it not yet ready, it will be set when when creation completes.
@@ -1607,6 +1804,8 @@
         void adjustPreviewLocation(boolean shiftUp, int offset);
         void setPreviewRotation(int orientation);
         void showOutgoingVideoView(boolean show);
+        void onRequestReadStoragePermission();
+        Point getPreviewContainerSize();
     }
 
     /**