2 * Copyright (C) 2012 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 package com.android.camera;
20 import android.app.AlertDialog;
21 import android.content.DialogInterface;
22 import android.graphics.Bitmap;
23 import android.graphics.Color;
24 import android.graphics.Matrix;
25 import android.graphics.SurfaceTexture;
26 import android.graphics.drawable.ColorDrawable;
27 import android.hardware.Camera;
28 import android.hardware.Camera.Face;
29 import android.os.AsyncTask;
30 import android.util.Log;
31 import android.view.Gravity;
32 import android.view.TextureView;
33 import android.view.View;
34 import android.view.View.OnClickListener;
35 import android.view.View.OnLayoutChangeListener;
36 import android.view.ViewGroup;
37 import android.view.ViewStub;
38 import android.widget.FrameLayout.LayoutParams;
39 import android.widget.ImageView;
40 import android.widget.PopupWindow;
41 import android.widget.Toast;
43 import com.android.camera.CameraPreference.OnPreferenceChangedListener;
44 import com.android.camera.FocusOverlayManager.FocusUI;
45 import com.android.camera.ui.ModuleSwitcher;
46 import com.android.camera.util.ApiHelper;
47 import com.android.camera.ui.AbstractSettingPopup;
48 import com.android.camera.ui.CameraControls;
49 import com.android.camera.ui.CameraRootView;
50 import com.android.camera.ui.CountDownView;
51 import com.android.camera.ui.CountDownView.OnCountDownFinishedListener;
52 import com.android.camera.ui.FaceView;
53 import com.android.camera.ui.FocusIndicator;
54 import com.android.camera.ui.PieRenderer;
55 import com.android.camera.ui.PieRenderer.PieListener;
56 import com.android.camera.ui.RenderOverlay;
57 import com.android.camera.ui.ZoomRenderer;
58 import com.android.camera.util.CameraUtil;
59 import com.android.camera2.R;
61 import java.util.List;
63 public class PhotoUI implements PieListener,
64 PreviewGestures.SingleTapListener,
65 FocusUI, TextureView.SurfaceTextureListener,
66 LocationManager.Listener, CameraRootView.MyDisplayListener,
67 CameraManager.CameraFaceDetectionCallback {
69 private static final String TAG = "CAM_UI";
70 private static final int DOWN_SAMPLE_FACTOR = 4;
71 private final AnimationManager mAnimationManager;
72 private CameraActivity mActivity;
73 private PhotoController mController;
74 private PreviewGestures mGestures;
76 private View mRootView;
77 private SurfaceTexture mSurfaceTexture;
79 private PopupWindow mPopup;
80 private ShutterButton mShutterButton;
81 private CountDownView mCountDownView;
83 private FaceView mFaceView;
84 private RenderOverlay mRenderOverlay;
85 private View mReviewCancelButton;
86 private View mReviewDoneButton;
87 private View mReviewRetakeButton;
89 private View mMenuButton;
90 private PhotoMenu mMenu;
91 private ModuleSwitcher mSwitcher;
92 private CameraControls mCameraControls;
93 private AlertDialog mLocationDialog;
95 // Small indicators which show the camera settings in the viewfinder.
96 private OnScreenIndicators mOnScreenIndicators;
98 private PieRenderer mPieRenderer;
99 private ZoomRenderer mZoomRenderer;
100 private Toast mNotSelectableToast;
102 private int mZoomMax;
103 private List<Integer> mZoomRatios;
105 private int mPreviewWidth = 0;
106 private int mPreviewHeight = 0;
107 private float mSurfaceTextureUncroppedWidth;
108 private float mSurfaceTextureUncroppedHeight;
110 private ImageView mPreviewThumb;
111 private View mFlashOverlay;
113 private SurfaceTextureSizeChangedListener mSurfaceTextureSizeListener;
114 private TextureView mTextureView;
115 private Matrix mMatrix = null;
116 private float mAspectRatio = 4f / 3f;
118 public interface SurfaceTextureSizeChangedListener {
119 public void onSurfaceTextureSizeChanged(int uncroppedWidth, int uncroppedHeight);
122 private OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() {
124 public void onLayoutChange(View v, int left, int top, int right,
125 int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
126 int width = right - left;
127 int height = bottom - top;
128 // Full-screen preview
131 if (CameraUtil.getDisplayRotation(mActivity) % 180 != 0) {
135 if (mPreviewWidth != width || mPreviewHeight != height) {
136 mPreviewWidth = width;
137 mPreviewHeight = height;
138 setTransformMatrix(width, height);
139 mController.onScreenSizeChanged(width, height, w, h);
144 private class DecodeTask extends AsyncTask<Integer, Void, Bitmap> {
145 private final byte [] mData;
146 private int mOrientation;
147 private boolean mMirror;
149 public DecodeTask(byte[] data, int orientation, boolean mirror) {
151 mOrientation = orientation;
156 protected Bitmap doInBackground(Integer... params) {
157 // Decode image in background.
158 Bitmap bitmap = CameraUtil.downSample(mData, DOWN_SAMPLE_FACTOR);
159 if (mOrientation != 0 || mMirror) {
160 Matrix m = new Matrix();
161 m.preRotate(mOrientation);
166 return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m,
173 protected void onPostExecute(Bitmap bitmap) {
174 mPreviewThumb.setImageBitmap(bitmap);
175 mAnimationManager.startCaptureAnimation(mPreviewThumb);
179 public PhotoUI(CameraActivity activity, PhotoController controller, View parent) {
180 mActivity = activity;
181 mController = controller;
184 mActivity.getLayoutInflater().inflate(R.layout.photo_module,
185 (ViewGroup) mRootView, true);
186 mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay);
187 mFlashOverlay = mRootView.findViewById(R.id.flash_overlay);
189 mTextureView = (TextureView) mRootView.findViewById(R.id.preview_content);
190 mTextureView.setSurfaceTextureListener(this);
191 mTextureView.addOnLayoutChangeListener(mLayoutListener);
194 mShutterButton = (ShutterButton) mRootView.findViewById(R.id.shutter_button);
195 mSwitcher = (ModuleSwitcher) mRootView.findViewById(R.id.camera_switcher);
196 mSwitcher.setCurrentIndex(ModuleSwitcher.PHOTO_MODULE_INDEX);
197 mSwitcher.setSwitchListener(mActivity);
198 mMenuButton = mRootView.findViewById(R.id.menu);
199 if (ApiHelper.HAS_FACE_DETECTION) {
200 ViewStub faceViewStub = (ViewStub) mRootView
201 .findViewById(R.id.face_view_stub);
202 if (faceViewStub != null) {
203 faceViewStub.inflate();
204 mFaceView = (FaceView) mRootView.findViewById(R.id.face_view);
205 setSurfaceTextureSizeChangedListener(mFaceView);
208 mCameraControls = (CameraControls) mRootView.findViewById(R.id.camera_controls);
209 ((CameraRootView) mRootView).setDisplayChangeListener(this);
210 mAnimationManager = new AnimationManager();
213 public void setSurfaceTextureSizeChangedListener(SurfaceTextureSizeChangedListener listener) {
214 mSurfaceTextureSizeListener = listener;
217 private void setTransformMatrix(int width, int height) {
218 mMatrix = mTextureView.getTransform(mMatrix);
219 float scaleX = 1f, scaleY = 1f;
220 float scaledTextureWidth, scaledTextureHeight;
221 if (width > height) {
222 scaledTextureWidth = Math.max(width,
223 (int) (height * mAspectRatio));
224 scaledTextureHeight = Math.max(height,
225 (int)(width / mAspectRatio));
227 scaledTextureWidth = Math.max(width,
228 (int) (height / mAspectRatio));
229 scaledTextureHeight = Math.max(height,
230 (int) (width * mAspectRatio));
233 if (mSurfaceTextureUncroppedWidth != scaledTextureWidth ||
234 mSurfaceTextureUncroppedHeight != scaledTextureHeight) {
235 mSurfaceTextureUncroppedWidth = scaledTextureWidth;
236 mSurfaceTextureUncroppedHeight = scaledTextureHeight;
237 if (mSurfaceTextureSizeListener != null) {
238 mSurfaceTextureSizeListener.onSurfaceTextureSizeChanged(
239 (int) mSurfaceTextureUncroppedWidth, (int) mSurfaceTextureUncroppedHeight);
242 scaleX = scaledTextureWidth / width;
243 scaleY = scaledTextureHeight / height;
244 mMatrix.setScale(scaleX, scaleY, (float) width / 2, (float) height / 2);
245 mTextureView.setTransform(mMatrix);
249 public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
250 Log.v(TAG, "SurfaceTexture ready.");
251 mSurfaceTexture = surface;
252 mController.onPreviewUIReady();
256 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
257 // Ignored, Camera does all the work for us
261 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
262 mSurfaceTexture = null;
263 mController.onPreviewUIDestroyed();
264 Log.w(TAG, "SurfaceTexture destroyed");
269 public void onSurfaceTextureUpdated(SurfaceTexture surface) {
273 public View getRootView() {
277 private void initIndicators() {
278 mOnScreenIndicators = new OnScreenIndicators(mActivity,
279 mRootView.findViewById(R.id.on_screen_indicators));
282 public void onCameraOpened(PreferenceGroup prefGroup, ComboPreferences prefs,
283 Camera.Parameters params, OnPreferenceChangedListener listener) {
284 if (mPieRenderer == null) {
285 mPieRenderer = new PieRenderer(mActivity);
286 mPieRenderer.setPieListener(this);
287 mRenderOverlay.addRenderer(mPieRenderer);
291 mMenu = new PhotoMenu(mActivity, this, mPieRenderer);
292 mMenu.setListener(listener);
294 mMenu.initialize(prefGroup);
296 if (mZoomRenderer == null) {
297 mZoomRenderer = new ZoomRenderer(mActivity);
298 mRenderOverlay.addRenderer(mZoomRenderer);
301 if (mGestures == null) {
302 // this will handle gesture disambiguation and dispatching
303 mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer);
304 mRenderOverlay.setGestures(mGestures);
306 mGestures.setZoomEnabled(params.isZoomSupported());
307 mGestures.setRenderOverlay(mRenderOverlay);
308 mRenderOverlay.requestLayout();
310 initializeZoom(params);
311 updateOnScreenIndicators(params, prefGroup, prefs);
314 public void animateCapture(final byte[] jpegData, int orientation, boolean mirror) {
315 // Decode jpeg byte array and then animate the jpeg
316 DecodeTask task = new DecodeTask(jpegData, orientation, mirror);
320 private void openMenu() {
321 if (mPieRenderer != null) {
322 // If autofocus is not finished, cancel autofocus so that the
323 // subsequent touch can be handled by PreviewGestures
324 if (mController.getCameraState() == PhotoController.FOCUSING) {
325 mController.cancelAutoFocus();
327 mPieRenderer.showInCenter();
331 public void initializeControlByIntent() {
332 mPreviewThumb = (ImageView) mRootView.findViewById(R.id.preview_thumb);
333 mPreviewThumb.setOnClickListener(new OnClickListener() {
335 public void onClick(View v) {
336 mActivity.gotoGallery();
339 mMenuButton = mRootView.findViewById(R.id.menu);
340 mMenuButton.setOnClickListener(new OnClickListener() {
342 public void onClick(View v) {
346 if (mController.isImageCaptureIntent()) {
348 ViewGroup cameraControls = (ViewGroup) mRootView.findViewById(R.id.camera_controls);
349 mActivity.getLayoutInflater().inflate(R.layout.review_module_control, cameraControls);
351 mReviewDoneButton = mRootView.findViewById(R.id.btn_done);
352 mReviewCancelButton = mRootView.findViewById(R.id.btn_cancel);
353 mReviewRetakeButton = mRootView.findViewById(R.id.btn_retake);
354 mReviewCancelButton.setVisibility(View.VISIBLE);
356 mReviewDoneButton.setOnClickListener(new OnClickListener() {
358 public void onClick(View v) {
359 mController.onCaptureDone();
362 mReviewCancelButton.setOnClickListener(new OnClickListener() {
364 public void onClick(View v) {
365 mController.onCaptureCancelled();
369 mReviewRetakeButton.setOnClickListener(new OnClickListener() {
371 public void onClick(View v) {
372 mController.onCaptureRetake();
378 public void hideUI() {
379 mCameraControls.setVisibility(View.INVISIBLE);
380 mSwitcher.closePopup();
383 public void showUI() {
384 mCameraControls.setVisibility(View.VISIBLE);
387 public void hideSwitcher() {
388 mSwitcher.closePopup();
389 mSwitcher.setVisibility(View.INVISIBLE);
392 public void showSwitcher() {
393 mSwitcher.setVisibility(View.VISIBLE);
395 // called from onResume but only the first time
396 public void initializeFirstTime() {
397 // Initialize shutter button.
398 mShutterButton.setImageResource(R.drawable.btn_new_shutter);
399 mShutterButton.setOnShutterButtonListener(mController);
400 mShutterButton.setVisibility(View.VISIBLE);
403 // called from onResume every other time
404 public void initializeSecondTime(Camera.Parameters params) {
405 initializeZoom(params);
406 if (mController.isImageCaptureIntent()) {
407 hidePostCaptureAlert();
410 mMenu.reloadPreferences();
414 public void showLocationDialog() {
415 mLocationDialog = new AlertDialog.Builder(mActivity)
416 .setTitle(R.string.remember_location_title)
417 .setMessage(R.string.remember_location_prompt)
418 .setPositiveButton(R.string.remember_location_yes,
419 new DialogInterface.OnClickListener() {
421 public void onClick(DialogInterface dialog, int arg1) {
422 mController.enableRecordingLocation(true);
423 mLocationDialog = null;
426 .setNegativeButton(R.string.remember_location_no,
427 new DialogInterface.OnClickListener() {
429 public void onClick(DialogInterface dialog, int arg1) {
433 .setOnCancelListener(new DialogInterface.OnCancelListener() {
435 public void onCancel(DialogInterface dialog) {
436 mController.enableRecordingLocation(false);
437 mLocationDialog = null;
443 public void initializeZoom(Camera.Parameters params) {
444 if ((params == null) || !params.isZoomSupported()
445 || (mZoomRenderer == null)) return;
446 mZoomMax = params.getMaxZoom();
447 mZoomRatios = params.getZoomRatios();
448 // Currently we use immediate zoom for fast zooming to get better UX and
449 // there is no plan to take advantage of the smooth zoom.
450 if (mZoomRenderer != null) {
451 mZoomRenderer.setZoomMax(mZoomMax);
452 mZoomRenderer.setZoom(params.getZoom());
453 mZoomRenderer.setZoomValue(mZoomRatios.get(params.getZoom()));
454 mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener());
459 public void showGpsOnScreenIndicator(boolean hasSignal) { }
462 public void hideGpsOnScreenIndicator() { }
464 public void overrideSettings(final String ... keyvalues) {
465 mMenu.overrideSettings(keyvalues);
468 public void updateOnScreenIndicators(Camera.Parameters params,
469 PreferenceGroup group, ComboPreferences prefs) {
470 if (params == null) return;
471 mOnScreenIndicators.updateSceneOnScreenIndicator(params.getSceneMode());
472 mOnScreenIndicators.updateExposureOnScreenIndicator(params,
473 CameraSettings.readExposure(prefs));
474 mOnScreenIndicators.updateFlashOnScreenIndicator(params.getFlashMode());
476 ListPreference pref = group.findPreference(CameraSettings.KEY_WHITE_BALANCE);
478 wbIndex = pref.getCurrentIndex();
480 mOnScreenIndicators.updateWBIndicator(wbIndex);
481 boolean location = RecordLocationPreference.get(
482 prefs, mActivity.getContentResolver());
483 mOnScreenIndicators.updateLocationIndicator(location);
486 public void setCameraState(int state) {
489 public void animateFlash() {
490 mAnimationManager.startFlashAnimation(mFlashOverlay);
493 public void enableGestures(boolean enable) {
494 if (mGestures != null) {
495 mGestures.setEnabled(enable);
499 // forward from preview gestures to controller
501 public void onSingleTapUp(View view, int x, int y) {
502 mController.onSingleTapUp(view, x, y);
505 public boolean onBackPressed() {
506 if (mPieRenderer != null && mPieRenderer.showsItems()) {
510 // In image capture mode, back button should:
511 // 1) if there is any popup, dismiss them, 2) otherwise, get out of
513 if (mController.isImageCaptureIntent()) {
514 mController.onCaptureCancelled();
516 } else if (!mController.isCameraIdle()) {
517 // ignore backs while we're taking a picture
524 public void onPreviewFocusChanged(boolean previewFocused) {
525 if (previewFocused) {
530 if (mFaceView != null) {
531 mFaceView.setBlockDraw(!previewFocused);
533 if (mGestures != null) {
534 mGestures.setEnabled(previewFocused);
536 if (mRenderOverlay != null) {
537 // this can not happen in capture mode
538 mRenderOverlay.setVisibility(previewFocused ? View.VISIBLE : View.GONE);
540 if (mPieRenderer != null) {
541 mPieRenderer.setBlockFocus(!previewFocused);
543 setShowMenu(previewFocused);
544 if (!previewFocused && mCountDownView != null) mCountDownView.cancelCountDown();
547 public void showPopup(AbstractSettingPopup popup) {
550 if (mPopup == null) {
551 mPopup = new PopupWindow(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
552 mPopup.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
553 mPopup.setOutsideTouchable(true);
554 mPopup.setFocusable(true);
555 mPopup.setOnDismissListener(new PopupWindow.OnDismissListener() {
557 public void onDismiss() {
559 mMenu.popupDismissed();
564 popup.setVisibility(View.VISIBLE);
565 mPopup.setContentView(popup);
566 mPopup.showAtLocation(mRootView, Gravity.CENTER, 0, 0);
569 public void dismissPopup() {
570 if (mPopup != null && mPopup.isShowing()) {
575 public void onShowSwitcherPopup() {
576 if (mPieRenderer != null && mPieRenderer.showsItems()) {
581 private void setShowMenu(boolean show) {
582 if (mOnScreenIndicators != null) {
583 mOnScreenIndicators.setVisibility(show ? View.VISIBLE : View.GONE);
585 if (mMenuButton != null) {
586 mMenuButton.setVisibility(show ? View.VISIBLE : View.GONE);
590 public boolean collapseCameraControls() {
591 // Remove all the popups/dialog boxes
593 if (mPopup != null) {
597 onShowSwitcherPopup();
601 protected void showPostCaptureAlert() {
602 mOnScreenIndicators.setVisibility(View.GONE);
603 mMenuButton.setVisibility(View.GONE);
604 CameraUtil.fadeIn(mReviewDoneButton);
605 mShutterButton.setVisibility(View.INVISIBLE);
606 CameraUtil.fadeIn(mReviewRetakeButton);
607 pauseFaceDetection();
610 protected void hidePostCaptureAlert() {
611 mOnScreenIndicators.setVisibility(View.VISIBLE);
612 mMenuButton.setVisibility(View.VISIBLE);
613 CameraUtil.fadeOut(mReviewDoneButton);
614 mShutterButton.setVisibility(View.VISIBLE);
615 CameraUtil.fadeOut(mReviewRetakeButton);
616 resumeFaceDetection();
619 public void setDisplayOrientation(int orientation) {
620 if (mFaceView != null) {
621 mFaceView.setDisplayOrientation(orientation);
625 // shutter button handling
627 public boolean isShutterPressed() {
628 return mShutterButton.isPressed();
632 * Enables or disables the shutter button.
634 public void enableShutter(boolean enabled) {
635 if (mShutterButton != null) {
636 mShutterButton.setEnabled(enabled);
640 public void pressShutterButton() {
641 if (mShutterButton.isInTouchMode()) {
642 mShutterButton.requestFocusFromTouch();
644 mShutterButton.requestFocus();
646 mShutterButton.setPressed(true);
649 private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener {
651 public void onZoomValueChanged(int index) {
652 int newZoom = mController.onZoomChanged(index);
653 if (mZoomRenderer != null) {
654 mZoomRenderer.setZoomValue(mZoomRatios.get(newZoom));
659 public void onZoomStart() {
660 if (mPieRenderer != null) {
661 mPieRenderer.setBlockFocus(true);
666 public void onZoomEnd() {
667 if (mPieRenderer != null) {
668 mPieRenderer.setBlockFocus(false);
674 public void onPieOpened(int centerX, int centerY) {
675 setSwipingEnabled(false);
676 if (mFaceView != null) {
677 mFaceView.setBlockDraw(true);
682 public void onPieClosed() {
683 setSwipingEnabled(true);
684 if (mFaceView != null) {
685 mFaceView.setBlockDraw(false);
689 public void setSwipingEnabled(boolean enable) {
690 mActivity.setSwipingEnabled(enable);
693 public SurfaceTexture getSurfaceTexture() {
694 return mSurfaceTexture;
699 private void initializeCountDown() {
700 mActivity.getLayoutInflater().inflate(R.layout.count_down_to_capture,
701 (ViewGroup) mRootView, true);
702 mCountDownView = (CountDownView) (mRootView.findViewById(R.id.count_down_to_capture));
703 mCountDownView.setCountDownFinishedListener((OnCountDownFinishedListener) mController);
706 public boolean isCountingDown() {
707 return mCountDownView != null && mCountDownView.isCountingDown();
710 public void cancelCountDown() {
711 if (mCountDownView == null) return;
712 mCountDownView.cancelCountDown();
715 public void startCountDown(int sec, boolean playSound) {
716 if (mCountDownView == null) initializeCountDown();
717 mCountDownView.startCountDown(sec, playSound);
720 public void showPreferencesToast() {
721 if (mNotSelectableToast == null) {
722 String str = mActivity.getResources().getString(R.string.not_selectable_in_scene_mode);
723 mNotSelectableToast = Toast.makeText(mActivity, str, Toast.LENGTH_SHORT);
725 mNotSelectableToast.show();
728 public void onPause() {
732 collapseCameraControls();
733 if (mFaceView != null) mFaceView.clear();
735 if (mLocationDialog != null && mLocationDialog.isShowing()) {
736 mLocationDialog.dismiss();
738 mLocationDialog = null;
743 // focus UI implementation
745 private FocusIndicator getFocusIndicator() {
746 return (mFaceView != null && mFaceView.faceExists()) ? mFaceView : mPieRenderer;
750 public boolean hasFaces() {
751 return (mFaceView != null && mFaceView.faceExists());
754 public void clearFaces() {
755 if (mFaceView != null) mFaceView.clear();
759 public void clearFocus() {
760 FocusIndicator indicator = getFocusIndicator();
761 if (indicator != null) indicator.clear();
765 public void setFocusPosition(int x, int y) {
766 mPieRenderer.setFocus(x, y);
770 public void onFocusStarted() {
771 getFocusIndicator().showStart();
775 public void onFocusSucceeded(boolean timeout) {
776 getFocusIndicator().showSuccess(timeout);
780 public void onFocusFailed(boolean timeout) {
781 getFocusIndicator().showFail(timeout);
785 public void pauseFaceDetection() {
786 if (mFaceView != null) mFaceView.pause();
790 public void resumeFaceDetection() {
791 if (mFaceView != null) mFaceView.resume();
794 public void onStartFaceDetection(int orientation, boolean mirror) {
796 mFaceView.setVisibility(View.VISIBLE);
797 mFaceView.setDisplayOrientation(orientation);
798 mFaceView.setMirror(mirror);
803 public void onFaceDetection(Face[] faces, CameraManager.CameraProxy camera) {
804 mFaceView.setFaces(faces);
808 public void onDisplayChanged() {
809 mCameraControls.checkLayoutFlip();
810 mController.updateCameraOrientation();