IMS-VT: Import zoom control bar source code and resources

- Add zoom control widget and resources

IMS-VT: Adding zoom widget to the video call UI

- Add zoom control and zoom control bar in UI. Add
  resource files for zoom slider, zoom in, alert dialog, etc

- Toggle zoom control when user clicks on camera preview

CRs-Fixed: 767934

IMS-VT: Catch the exception when dismissing the zoom alert dialog

- When we dismiss the zoom alert dialog, any exception caused
  should not crash the phone process

- Catch any exception that occurs when dismissing the zoom alert
  dialog and log it instead of letting it crash phone process

Change-Id: Ib0664d080814273e8135e04662b5e8595bbbba93
CRs-Fixed: 938992
diff --git a/InCallUI/res/drawable-hdpi/ic_zoom_big.9.png b/InCallUI/res/drawable-hdpi/ic_zoom_big.9.png
new file mode 100644
index 0000000..8c6cdea
--- /dev/null
+++ b/InCallUI/res/drawable-hdpi/ic_zoom_big.9.png
Binary files differ
diff --git a/InCallUI/res/drawable-hdpi/ic_zoom_big_dark.9.png b/InCallUI/res/drawable-hdpi/ic_zoom_big_dark.9.png
new file mode 100644
index 0000000..63ba20e
--- /dev/null
+++ b/InCallUI/res/drawable-hdpi/ic_zoom_big_dark.9.png
Binary files differ
diff --git a/InCallUI/res/drawable-hdpi/ic_zoom_in_holo_dark.png b/InCallUI/res/drawable-hdpi/ic_zoom_in_holo_dark.png
new file mode 100644
index 0000000..89b5f15
--- /dev/null
+++ b/InCallUI/res/drawable-hdpi/ic_zoom_in_holo_dark.png
Binary files differ
diff --git a/InCallUI/res/drawable-hdpi/ic_zoom_in_holo_light.png b/InCallUI/res/drawable-hdpi/ic_zoom_in_holo_light.png
new file mode 100644
index 0000000..9751ca3
--- /dev/null
+++ b/InCallUI/res/drawable-hdpi/ic_zoom_in_holo_light.png
Binary files differ
diff --git a/InCallUI/res/drawable-hdpi/ic_zoom_out_holo_dark.png b/InCallUI/res/drawable-hdpi/ic_zoom_out_holo_dark.png
new file mode 100644
index 0000000..f4a2589
--- /dev/null
+++ b/InCallUI/res/drawable-hdpi/ic_zoom_out_holo_dark.png
Binary files differ
diff --git a/InCallUI/res/drawable-hdpi/ic_zoom_out_holo_light.png b/InCallUI/res/drawable-hdpi/ic_zoom_out_holo_light.png
new file mode 100644
index 0000000..ba094ac
--- /dev/null
+++ b/InCallUI/res/drawable-hdpi/ic_zoom_out_holo_light.png
Binary files differ
diff --git a/InCallUI/res/drawable-hdpi/ic_zoom_slider.png b/InCallUI/res/drawable-hdpi/ic_zoom_slider.png
new file mode 100644
index 0000000..8427e4d
--- /dev/null
+++ b/InCallUI/res/drawable-hdpi/ic_zoom_slider.png
Binary files differ
diff --git a/InCallUI/res/drawable-mdpi/ic_zoom_big.9.png b/InCallUI/res/drawable-mdpi/ic_zoom_big.9.png
new file mode 100644
index 0000000..f5e31b4
--- /dev/null
+++ b/InCallUI/res/drawable-mdpi/ic_zoom_big.9.png
Binary files differ
diff --git a/InCallUI/res/drawable-mdpi/ic_zoom_big_dark.9.png b/InCallUI/res/drawable-mdpi/ic_zoom_big_dark.9.png
new file mode 100644
index 0000000..919db3f
--- /dev/null
+++ b/InCallUI/res/drawable-mdpi/ic_zoom_big_dark.9.png
Binary files differ
diff --git a/InCallUI/res/drawable-mdpi/ic_zoom_in_holo_dark.png b/InCallUI/res/drawable-mdpi/ic_zoom_in_holo_dark.png
new file mode 100644
index 0000000..4f33278
--- /dev/null
+++ b/InCallUI/res/drawable-mdpi/ic_zoom_in_holo_dark.png
Binary files differ
diff --git a/InCallUI/res/drawable-mdpi/ic_zoom_in_holo_light.png b/InCallUI/res/drawable-mdpi/ic_zoom_in_holo_light.png
new file mode 100644
index 0000000..3238a39
--- /dev/null
+++ b/InCallUI/res/drawable-mdpi/ic_zoom_in_holo_light.png
Binary files differ
diff --git a/InCallUI/res/drawable-mdpi/ic_zoom_out_holo_dark.png b/InCallUI/res/drawable-mdpi/ic_zoom_out_holo_dark.png
new file mode 100644
index 0000000..2631894
--- /dev/null
+++ b/InCallUI/res/drawable-mdpi/ic_zoom_out_holo_dark.png
Binary files differ
diff --git a/InCallUI/res/drawable-mdpi/ic_zoom_out_holo_light.png b/InCallUI/res/drawable-mdpi/ic_zoom_out_holo_light.png
new file mode 100644
index 0000000..8113dab
--- /dev/null
+++ b/InCallUI/res/drawable-mdpi/ic_zoom_out_holo_light.png
Binary files differ
diff --git a/InCallUI/res/drawable-mdpi/ic_zoom_slider.png b/InCallUI/res/drawable-mdpi/ic_zoom_slider.png
new file mode 100644
index 0000000..16aacf1
--- /dev/null
+++ b/InCallUI/res/drawable-mdpi/ic_zoom_slider.png
Binary files differ
diff --git a/InCallUI/res/drawable/ic_zoom_in.xml b/InCallUI/res/drawable/ic_zoom_in.xml
new file mode 100644
index 0000000..d441630
--- /dev/null
+++ b/InCallUI/res/drawable/ic_zoom_in.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2012 - 2015, The Linux Foundation. All rights reserved.
+     Not a Contribution, Apache license notifications and license are retained
+     for attribution purposes only.
+
+     Copyright (C) 2011 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_activated="true" android:drawable="@drawable/ic_zoom_in_holo_light" />
+    <item android:drawable="@drawable/ic_zoom_in_holo_dark" />
+</selector>
+
diff --git a/InCallUI/res/drawable/ic_zoom_out.xml b/InCallUI/res/drawable/ic_zoom_out.xml
new file mode 100644
index 0000000..1211c33
--- /dev/null
+++ b/InCallUI/res/drawable/ic_zoom_out.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2012 - 2105, The Linux Foundation. All rights reserved.
+     Not a Contribution, Apache license notifications and license are retained
+     for attribution purposes only.
+
+     Copyright (C) 2011 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_activated="true" android:drawable="@drawable/ic_zoom_out_holo_light" />
+    <item android:drawable="@drawable/ic_zoom_out_holo_dark" />
+</selector>
+
diff --git a/InCallUI/res/drawable/zoom_slider_bar.xml b/InCallUI/res/drawable/zoom_slider_bar.xml
new file mode 100644
index 0000000..923e4ba
--- /dev/null
+++ b/InCallUI/res/drawable/zoom_slider_bar.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (c) 2012 - 2015, The Linux Foundation. All rights reserved.
+     Not a Contribution, Apache license notifications and license are retained
+     for attribution purposes only.
+
+     Copyright (C) 2011 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_activated="true" android:drawable="@drawable/ic_zoom_big" />
+    <item android:drawable="@drawable/ic_zoom_big_dark" />
+</selector>
+
diff --git a/InCallUI/res/layout/qti_video_call_zoom_control.xml b/InCallUI/res/layout/qti_video_call_zoom_control.xml
new file mode 100644
index 0000000..51bc907
--- /dev/null
+++ b/InCallUI/res/layout/qti_video_call_zoom_control.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ 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.
+  ~
+  -->
+
+<!-- The xml contains Qti zoom control resource -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="20dp"
+    android:theme="@style/Theme.InCallScreen" >
+
+    <com.android.incallui.ZoomControlBar
+        android:id="@+id/zoom_control"
+        android:layout_gravity="center|clip_horizontal|clip_vertical"
+        android:layout_width="match_parent"
+        android:visibility="visible"
+        android:layout_height="20dp" />
+</FrameLayout>
diff --git a/InCallUI/src/com/android/incallui/InCallPresenter.java b/InCallUI/src/com/android/incallui/InCallPresenter.java
index 24b7db4..8da0c07 100644
--- a/InCallUI/src/com/android/incallui/InCallPresenter.java
+++ b/InCallUI/src/com/android/incallui/InCallPresenter.java
@@ -348,6 +348,7 @@
         InCallMessageController.getInstance().setUp(mContext);
         addDetailsListener(CallSubstateNotifier.getInstance());
 
+        InCallZoomController.getInstance().setUp(mContext);
         Log.d(this, "Finished InCallPresenter.setUp");
     }
 
@@ -372,6 +373,8 @@
 
         InCallMessageController.getInstance().tearDown();
         removeDetailsListener(CallSubstateNotifier.getInstance());
+
+        InCallZoomController.getInstance().tearDown();
     }
 
     private void attemptFinishActivity() {
diff --git a/InCallUI/src/com/android/incallui/InCallZoomController.java b/InCallUI/src/com/android/incallui/InCallZoomController.java
new file mode 100644
index 0000000..b44e99e
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/InCallZoomController.java
@@ -0,0 +1,250 @@
+/* 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.Context;
+import android.view.View;
+import android.telecom.InCallService.VideoCall;
+import android.app.AlertDialog;
+import android.view.LayoutInflater;
+import android.view.Window;
+import android.view.WindowManager;
+import org.codeaurora.ims.QtiCallConstants;
+import android.hardware.Camera;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraManager;
+import java.lang.Integer;
+import java.util.Objects;
+
+import com.android.incallui.ZoomControl.OnZoomChangedListener;
+
+
+/**
+ * This class implements the zoom listener for zoom control and shows the dialog and zoom controls
+ * on the InCall screen and maintains state info about the camera zoom index.
+ */
+public class InCallZoomController implements InCallPresenter.IncomingCallListener {
+
+    private static InCallZoomController sInCallZoomController;
+
+    private AlertDialog mAlertDialog;
+
+    private InCallPresenter mInCallPresenter;
+
+    private Context mContext;
+
+    private String mCameraId;
+
+    CameraManager mCameraManager;
+
+    /**
+     * This class implements the zoom listener for zoom control
+     */
+    private class ZoomChangeListener implements ZoomControl.OnZoomChangedListener {
+        private VideoCall mVideoCall;
+
+        public ZoomChangeListener(VideoCall videoCall) {
+            mVideoCall = videoCall;
+        }
+
+        @Override
+        public void onZoomValueChanged(int index) {
+            Log.v("this", "onZoomValueChanged:  index = " + index);
+            mZoomIndex = index;
+            mVideoCall.setZoom(mZoomIndex);
+        }
+    }
+
+    /**
+     * Default zoom value for camera
+     */
+    private static final int DEFAULT_CAMERA_ZOOM_VALUE = 0;
+
+    /**
+     * Transparency value for alert dialog
+     */
+    private static final float DIALOG_ALPHA_INDEX = 0.6f;
+
+    /**
+     * Static variable for storing zoom index value to maintain state
+     */
+    private int mZoomIndex = DEFAULT_CAMERA_ZOOM_VALUE;
+
+    /**
+     * This method returns a singleton instance of {@class InCallZoomController}
+     */
+    public static synchronized InCallZoomController getInstance() {
+        if (sInCallZoomController == null) {
+            sInCallZoomController = new InCallZoomController();
+        }
+        return sInCallZoomController;
+    }
+
+    /**
+     * Private constructor. Must use getInstance() to get this singleton.
+     */
+    private InCallZoomController() {
+    }
+
+    /**
+     * Set up function called to add listener for camera selection changes
+     */
+    public void setUp(Context context) {
+        mContext = context;
+        mInCallPresenter = InCallPresenter.getInstance();
+        mInCallPresenter.addIncomingCallListener(this);
+        mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
+    }
+
+    /**
+     * Tear down function to reset all variables and remove camera selection listener
+     */
+    public void tearDown() {
+        mAlertDialog = null;
+        mContext = null;
+        mCameraId = null;
+        mZoomIndex = DEFAULT_CAMERA_ZOOM_VALUE;
+        mInCallPresenter.removeIncomingCallListener(this);
+        mInCallPresenter = null;
+        mCameraManager = null;
+    }
+
+    /**
+     * Sets the layout params for the alert dialog - transparency and clearing flag to dim
+     * background UI
+     */
+    private static void setLayoutParams(AlertDialog alertDialog) {
+        if (alertDialog == null) {
+            return;
+        }
+        final Window window = alertDialog.getWindow();
+        WindowManager.LayoutParams windowLayoutParams = window.getAttributes();
+        windowLayoutParams.alpha = DIALOG_ALPHA_INDEX;
+        window.setAttributes(windowLayoutParams);
+        window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+    }
+
+    /**
+     * Called when preview surface is clicked on the InCallUI screen. Notification comes from
+     * {@class VideocallPresenter}. Create the alert dialog and the zoom control,
+     * set layout params attributes, set zoom params if zoom is supported and video call is valid
+     */
+    public void onPreviewSurfaceClicked(VideoCall videoCall) {
+        Log.d(this, "onPreviewSurfaceClicked: VideoCall - " + videoCall);
+
+        if(videoCall == null || !isCameraZoomSupported()) {
+            Log.e(this, "onPreviewSurfaceClicked: VideoCall is null or Zoom not supported ");
+            return;
+        }
+
+        try {
+            final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(
+                    mInCallPresenter.getActivity(), AlertDialog.THEME_HOLO_DARK);
+            final View zoomControlView = mInCallPresenter.getActivity().getLayoutInflater().
+                    inflate(R.layout.qti_video_call_zoom_control, null);
+            final ZoomControlBar zoomControl = (ZoomControlBar) zoomControlView.findViewById(
+                    R.id.zoom_control);
+            dialogBuilder.setView(zoomControlView);
+            mAlertDialog = dialogBuilder.create();
+            mAlertDialog.setCanceledOnTouchOutside(true);
+            setLayoutParams(mAlertDialog);
+            zoomControl.setOnZoomChangeListener(new ZoomChangeListener(videoCall));
+            initZoomControl(zoomControl, mZoomIndex);
+            mAlertDialog.show();
+        } catch (Exception e) {
+            Log.e(this, "onPreviewSurfaceClicked: Exception " + e);
+            return;
+        }
+    }
+
+    private static void initZoomControl(ZoomControlBar zoomControl, int zoomIndex) {
+        zoomControl.setZoomMax(QtiCallConstants.CAMERA_MAX_ZOOM);
+        zoomControl.setZoomIndex(zoomIndex);
+        zoomControl.setEnabled(true);
+    }
+
+    /**
+     * Queries the camera characteristics to figure out if zoom is supported or not
+     */
+    private boolean isCameraZoomSupported() {
+        try {
+            final InCallCameraManager inCallCameraManager = mInCallPresenter.
+                    getInCallCameraManager();
+            final float CAMERA_ZOOM_NOT_SUPPORTED = 1.0f;
+
+            CameraCharacteristics characteristics = mCameraManager.getCameraCharacteristics(
+                inCallCameraManager.getActiveCameraId());
+            return (characteristics != null) && (characteristics.get(
+                    CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM)
+                    > CAMERA_ZOOM_NOT_SUPPORTED);
+        } catch (Exception e) {
+            Log.e(this, "isCameraZoomSupported: Failed to retrieve Max Zoom, " + e);
+            return false;
+        }
+    }
+
+    /**
+     * Called from the {@class VideoCallPresenter} when camera is enabled or disabled
+     * Reset the zoom index and dismiss the alert if camera id changes
+     */
+    public void onCameraEnabled(String cameraId) {
+        Log.d(this, "onCameraEnabled: - cameraId -" + cameraId);
+        if (!Objects.equals(mCameraId, cameraId)) {
+            mCameraId = cameraId;
+            mZoomIndex = DEFAULT_CAMERA_ZOOM_VALUE;
+            dismissAlertDialog();
+        }
+    }
+
+    private void dismissAlertDialog() {
+        try {
+            if (mAlertDialog != null) {
+                mAlertDialog.dismiss();
+                mAlertDialog = null;
+            }
+        } catch (Exception e) {
+            // Since exceptions caused in zoom control dialog should not crash the phone process,
+            // we intentionally capture the exception and ignore.
+            Log.e(this, "dismissAlertDialog: Exception: " + e);
+        }
+    }
+
+    /**
+     * Called when there is a new incoming call.
+     * Dismiss the alert.
+     */
+    @Override
+    public void onIncomingCall(InCallPresenter.InCallState oldState,
+            InCallPresenter.InCallState newState, Call call) {
+        Log.v(this, "onIncomingCall - Call " + call + "oldState " + oldState + "newState " +
+                newState);
+        dismissAlertDialog();
+    }
+}
diff --git a/InCallUI/src/com/android/incallui/VideoCallPresenter.java b/InCallUI/src/com/android/incallui/VideoCallPresenter.java
index e6d2646..32ad4e8 100644
--- a/InCallUI/src/com/android/incallui/VideoCallPresenter.java
+++ b/InCallUI/src/com/android/incallui/VideoCallPresenter.java
@@ -366,15 +366,25 @@
     }
 
     /**
-     * Handles clicks on the video surfaces by toggling full screen state.
-     * Informs the {@link InCallPresenter} of the change so that it can inform the
-     * {@link CallCardPresenter} of the change.
+     * Handles clicks on the video surfaces by toggling full screen state if surface is
+     * SURFACE_DISPLAY. Call onPreviewSurfaceClicked of InCallZoomController if preview surface
+     * is clicked. Informs the {@link InCallPresenter} of the change for Display surface so that
+     * it can inform the {@link CallCardPresenter} of the change.
      *
      * @param surfaceId The video surface receiving the click.
      */
     public void onSurfaceClick(int surfaceId) {
-        boolean isFullscreen = InCallPresenter.getInstance().toggleFullscreenMode();
-        Log.v(this, "toggleFullScreen = " + isFullscreen);
+        switch (surfaceId) {
+            case VideoCallFragment.SURFACE_DISPLAY:
+                boolean isFullscreen = InCallPresenter.getInstance().toggleFullscreenMode();
+                Log.d(this, "toggleFullScreen = " + isFullscreen);
+                break;
+            case VideoCallFragment.SURFACE_PREVIEW:
+                InCallZoomController.getInstance().onPreviewSurfaceClicked(mVideoCall);
+                break;
+            default:
+                break;
+        }
     }
 
     /**
@@ -723,9 +733,11 @@
             mPreviewSurfaceState = PreviewSurfaceState.CAMERA_SET;
 
             videoCall.requestCameraCapabilities();
+            InCallZoomController.getInstance().onCameraEnabled(cameraManager.getActiveCameraId());
         } else {
             mPreviewSurfaceState = PreviewSurfaceState.NONE;
             videoCall.setCamera(null);
+            InCallZoomController.getInstance().onCameraEnabled(null);
         }
     }
 
diff --git a/InCallUI/src/com/android/incallui/ZoomControl.java b/InCallUI/src/com/android/incallui/ZoomControl.java
new file mode 100644
index 0000000..7e25b98
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/ZoomControl.java
@@ -0,0 +1,132 @@
+/* Copyright (c) 2012 - 2015, The Linux Foundation. All rights reserved.
+ * Not a Contribution, Apache license notifications and license are retained
+ * for attribution purposes only.
+ *
+ * Copyright (C) 2011 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.os.Handler;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+
+/**
+ * A view that contains camera zoom control which could adjust the zoom in/out
+ * if the camera supports zooming.
+ */
+public abstract class ZoomControl extends RelativeLayout{
+    protected ImageView mZoomIn;
+    protected ImageView mZoomOut;
+    protected ImageView mZoomSlider;
+    protected int mOrientation;
+
+    public interface OnZoomChangedListener {
+        void onZoomValueChanged(int index);  // only for immediate zoom
+    }
+
+    // The interface OnZoomIndexChangedListener is used to inform the
+    // ZoomIndexBar about the zoom index change. The index position is between
+    // 0 (the index is zero) and 1.0 (the index is mZoomMax).
+    public interface OnZoomIndexChangedListener {
+        void onZoomIndexChanged(double indexPosition);
+    }
+
+    protected int mZoomMax, mZoomIndex;
+    private OnZoomChangedListener mListener;
+
+    private int mStep;
+
+    public ZoomControl(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mZoomIn = addImageView(context, R.drawable.ic_zoom_in);
+        mZoomSlider = addImageView(context, R.drawable.ic_zoom_slider);
+        mZoomOut = addImageView(context, R.drawable.ic_zoom_out);
+    }
+
+    public void startZoomControl() {
+        mZoomSlider.setPressed(true);
+        setZoomIndex(mZoomIndex); // Update the zoom index bar.
+    }
+
+    protected ImageView addImageView(Context context, int iconResourceId) {
+        ImageView image = new ImageView(context);
+        image.setImageResource(iconResourceId);
+        addView(image);
+        return image;
+    }
+
+    public void closeZoomControl() {
+        mZoomSlider.setPressed(false);
+    }
+
+    public void setZoomMax(int zoomMax) {
+        mZoomMax = zoomMax;
+
+        // Layout should be requested as the maximum zoom level is the key to
+        // show the correct zoom slider position.
+        requestLayout();
+    }
+
+    public int getZoomMax() {
+        return mZoomMax;
+    }
+
+    public void setOnZoomChangeListener(OnZoomChangedListener listener) {
+        mListener = listener;
+    }
+
+    public void setZoomIndex(int index) {
+        if (index < 0 || index > mZoomMax) {
+            throw new IllegalArgumentException("Invalid zoom value:" + index);
+        }
+        mZoomIndex = index;
+        invalidate();
+    }
+
+    public int getZoomIndex() {
+        return mZoomIndex;
+    }
+
+    protected void setZoomStep(int step) {
+        mStep = step;
+    }
+
+    // Called from ZoomControlBar to change the zoom level.
+    protected void performZoom(double zoomPercentage) {
+        int index = (int) (mZoomMax * zoomPercentage);
+        if (mZoomIndex == index) return;
+        changeZoomIndex(index);
+   }
+
+    private boolean changeZoomIndex(int index) {
+        if (mListener != null) {
+            if (index > mZoomMax) index = mZoomMax;
+            if (index < 0) index = 0;
+            mListener.onZoomValueChanged(index);
+            mZoomIndex = index;
+        }
+        return true;
+    }
+
+    @Override
+    public void setActivated(boolean activated) {
+        super.setActivated(activated);
+        mZoomIn.setActivated(activated);
+        mZoomOut.setActivated(activated);
+    }
+}
diff --git a/InCallUI/src/com/android/incallui/ZoomControlBar.java b/InCallUI/src/com/android/incallui/ZoomControlBar.java
new file mode 100644
index 0000000..8a89218
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/ZoomControlBar.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2012 - 2015, The Linux Foundation. All rights reserved.
+ * Not a Contribution, Apache license notifications and license are retained
+ * for attribution purposes only.
+ *
+ * Copyright (C) 2011 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.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * A view that contains camera zoom control and its layout.
+ */
+public class ZoomControlBar extends ZoomControl {
+    private static final int THRESHOLD_FIRST_MOVE = 10; // pixels
+    // Space between indicator icon and the zoom-in/out icon.
+    private static final int ICON_SPACING = 12;
+
+    private View mBar;
+    private boolean mStartChanging;
+    private static int mSliderPosition = 0;
+    private int mSliderLength;
+    private int mWidth;
+    private int mIconWidth;
+    private int mTotalIconWidth;
+
+    public ZoomControlBar(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        mBar = new View(context);
+        mBar.setBackgroundResource(R.drawable.zoom_slider_bar);
+        addView(mBar);
+    }
+
+    @Override
+    public void setActivated(boolean activated) {
+        super.setActivated(activated);
+        mBar.setActivated(activated);
+    }
+
+    private int getSliderPosition(int x) {
+        // Calculate the absolute offset of the slider in the zoom control bar.
+        // For left-hand users, as the device is rotated for 180 degree for
+        // landscape mode, the zoom-in bottom should be on the top, so the
+        // position should be reversed.
+        int pos; // the relative position in the zoom slider bar
+        if (mOrientation == 90) {
+            pos = mWidth - mTotalIconWidth - x;
+        } else {
+            pos = x - mTotalIconWidth;
+        }
+        if (pos < 0) pos = 0;
+        if (pos > mSliderLength) pos = mSliderLength;
+        return pos;
+    }
+
+    @Override
+    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+        mWidth = w;
+        mIconWidth = mZoomIn.getMeasuredWidth();
+        mTotalIconWidth = mIconWidth + ICON_SPACING;
+        mSliderLength = mWidth  - (2 * mTotalIconWidth);
+    }
+
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent event) {
+        if (!isEnabled() || (mWidth == 0)) return false;
+        int action = event.getAction();
+
+        switch (action) {
+            case MotionEvent.ACTION_OUTSIDE:
+            case MotionEvent.ACTION_CANCEL:
+            case MotionEvent.ACTION_UP:
+                setActivated(false);
+                closeZoomControl();
+                break;
+
+            case MotionEvent.ACTION_DOWN:
+                setActivated(true);
+                mStartChanging = false;
+            case MotionEvent.ACTION_MOVE:
+                int pos = getSliderPosition((int) event.getX());
+                if (!mStartChanging) {
+                    // Make sure the movement is large enough before we start
+                    // changing the zoom.
+                    int delta = mSliderPosition - pos;
+                    if ((delta > THRESHOLD_FIRST_MOVE) ||
+                            (delta < -THRESHOLD_FIRST_MOVE)) {
+                        mStartChanging = true;
+                    }
+                }
+                if (mStartChanging) {
+                    performZoom(1.0d * pos / mSliderLength);
+                    mSliderPosition = pos;
+                }
+                requestLayout();
+        }
+        return true;
+    }
+
+    @Override
+    protected void onLayout(
+            boolean changed, int left, int top, int right, int bottom) {
+        if (mZoomMax == 0) return;
+        int height = bottom - top;
+        mBar.layout(mTotalIconWidth, 0, mWidth - mTotalIconWidth, height);
+        // For left-hand users, as the device is rotated for 180 degree,
+        // the zoom-in button should be on the top.
+        int pos; // slider position
+        int sliderPosition;
+        if (mSliderPosition != -1) { // -1 means invalid
+            sliderPosition = mSliderPosition;
+        } else {
+            sliderPosition = (int) ((double) mSliderLength * mZoomIndex / mZoomMax);
+        }
+        if (mOrientation == 90) {
+            mZoomIn.layout(0, 0, mIconWidth, height);
+            mZoomOut.layout(mWidth - mIconWidth, 0, mWidth, height);
+            pos = mBar.getRight() - sliderPosition;
+        } else {
+            mZoomOut.layout(0, 0, mIconWidth, height);
+            mZoomIn.layout(mWidth - mIconWidth, 0, mWidth, height);
+            pos = mBar.getLeft() + sliderPosition;
+        }
+        int sliderWidth = mZoomSlider.getMeasuredWidth();
+        mZoomSlider.layout((pos - sliderWidth / 2), 0,
+                (pos + sliderWidth / 2), height);
+    }
+
+    @Override
+    public void setZoomIndex(int index) {
+        super.setZoomIndex(index);
+        mSliderPosition = -1; // -1 means invalid
+        requestLayout();
+    }
+}