CaptureModule.java revision 86eb60744b65f90d1f710585007b300c11181fa3
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
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
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16
17package com.android.camera;
18
19import android.content.Context;
20import android.graphics.Matrix;
21import android.graphics.Point;
22import android.graphics.RectF;
23import android.graphics.SurfaceTexture;
24import android.location.Location;
25import android.media.MediaActionSound;
26import android.net.Uri;
27import android.os.Handler;
28import android.os.HandlerThread;
29import android.os.SystemClock;
30import android.view.GestureDetector;
31import android.view.KeyEvent;
32import android.view.MotionEvent;
33import android.view.Surface;
34import android.view.View;
35
36import com.android.camera.app.AppController;
37import com.android.camera.app.CameraAppUI;
38import com.android.camera.app.CameraAppUI.BottomBarUISpec;
39import com.android.camera.app.LocationManager;
40import com.android.camera.app.OrientationManager.DeviceOrientation;
41import com.android.camera.async.MainThread;
42import com.android.camera.burst.BurstFacade;
43import com.android.camera.burst.BurstFacadeFactory;
44import com.android.camera.burst.BurstReadyStateChangeListener;
45import com.android.camera.burst.OrientationLockController;
46import com.android.camera.captureintent.PreviewTransformCalculator;
47import com.android.camera.debug.DebugPropertyHelper;
48import com.android.camera.debug.Log;
49import com.android.camera.debug.Log.Tag;
50import com.android.camera.device.CameraId;
51import com.android.camera.hardware.HardwareSpec;
52import com.android.camera.hardware.HeadingSensor;
53import com.android.camera.module.ModuleController;
54import com.android.camera.one.OneCamera;
55import com.android.camera.one.OneCamera.AutoFocusState;
56import com.android.camera.one.OneCamera.CaptureReadyCallback;
57import com.android.camera.one.OneCamera.Facing;
58import com.android.camera.one.OneCamera.OpenCallback;
59import com.android.camera.one.OneCamera.PhotoCaptureParameters;
60import com.android.camera.one.OneCameraAccessException;
61import com.android.camera.one.OneCameraCaptureSetting;
62import com.android.camera.one.OneCameraCharacteristics;
63import com.android.camera.one.OneCameraException;
64import com.android.camera.one.OneCameraManager;
65import com.android.camera.one.OneCameraModule;
66import com.android.camera.one.OneCameraOpener;
67import com.android.camera.one.config.OneCameraFeatureConfig;
68import com.android.camera.one.v2.photo.ImageRotationCalculator;
69import com.android.camera.one.v2.photo.ImageRotationCalculatorImpl;
70import com.android.camera.remote.RemoteCameraModule;
71import com.android.camera.session.CaptureSession;
72import com.android.camera.settings.Keys;
73import com.android.camera.settings.SettingsManager;
74import com.android.camera.stats.UsageStatistics;
75import com.android.camera.stats.profiler.Profile;
76import com.android.camera.stats.profiler.Profiler;
77import com.android.camera.stats.profiler.Profilers;
78import com.android.camera.ui.CountDownView;
79import com.android.camera.ui.PreviewStatusListener;
80import com.android.camera.ui.TouchCoordinate;
81import com.android.camera.ui.focus.FocusController;
82import com.android.camera.ui.focus.FocusSound;
83import com.android.camera.util.AndroidServices;
84import com.android.camera.util.ApiHelper;
85import com.android.camera.util.CameraUtil;
86import com.android.camera.util.GcamHelper;
87import com.android.camera.util.Size;
88import com.android.camera2.R;
89import com.android.ex.camera2.portability.CameraAgent.CameraProxy;
90import com.google.common.logging.eventprotos;
91
92import java.util.concurrent.Semaphore;
93import java.util.concurrent.TimeUnit;
94
95/**
96 * New Capture module that is made to support photo and video capture on top of
97 * the OneCamera API, to transparently support GCam.
98 * <p>
99 * This has been a re-write with pieces taken and improved from GCamModule and
100 * PhotoModule, which are to be retired eventually.
101 * <p>
102 */
103public class CaptureModule extends CameraModule implements
104        ModuleController,
105        CountDownView.OnCountDownStatusListener,
106        OneCamera.PictureCallback,
107        OneCamera.FocusStateListener,
108        OneCamera.ReadyStateChangedListener,
109        RemoteCameraModule {
110
111    private static final Tag TAG = new Tag("CaptureModule");
112    /** Enable additional debug output. */
113    private static final boolean DEBUG = true;
114    /** Workaround Flag for b/19271661 to use autotransformation in Capture Layout in Nexus4 **/
115    private static final boolean USE_AUTOTRANSFORM_UI_LAYOUT = ApiHelper.IS_NEXUS_4;
116
117    /** Timeout for camera open/close operations. */
118    private static final int CAMERA_OPEN_CLOSE_TIMEOUT_MILLIS = 2500;
119
120    /** System Properties switch to enable debugging focus UI. */
121    private static final boolean CAPTURE_DEBUG_UI = DebugPropertyHelper.showCaptureDebugUI();
122
123    private final Object mDimensionLock = new Object();
124
125    /**
126     * Sticky Gcam mode is when this module's sole purpose it to be the Gcam
127     * mode. If true, the device uses {@link PhotoModule} for normal picture
128     * taking.
129     */
130    private final boolean mStickyGcamCamera;
131
132    /** Controller giving us access to other services. */
133    private final AppController mAppController;
134    /** The applications settings manager. */
135    private final SettingsManager mSettingsManager;
136    /** Application context. */
137    private final Context mContext;
138    /** Module UI. */
139    private CaptureModuleUI mUI;
140    /** The camera manager used to open cameras. */
141    private OneCameraOpener mOneCameraOpener;
142    /** The manager to query for camera device information */
143    private OneCameraManager mOneCameraManager;
144    /** The currently opened camera device, or null if the camera is closed. */
145    private OneCamera mCamera;
146    /** The selected picture size. */
147    private Size mPictureSize;
148    /** Held when opening or closing the camera. */
149    private final Semaphore mCameraOpenCloseLock = new Semaphore(1);
150    /** The direction the currently opened camera is facing to. */
151    private Facing mCameraFacing;
152    /** Whether HDR Scene mode is currently enabled. */
153    private boolean mHdrSceneEnabled = false;
154    private boolean mHdrPlusEnabled = false;
155    private final Object mSurfaceTextureLock = new Object();
156    /**
157     * Flag that is used when Fatal Error Handler is running and the app should
158     * not continue execution
159     */
160    private boolean mShowErrorAndFinish;
161    private TouchCoordinate mLastShutterTouchCoordinate = null;
162
163    private FocusController mFocusController;
164    private OneCameraCharacteristics mCameraCharacteristics;
165    final private PreviewTransformCalculator mPreviewTransformCalculator;
166
167    /** The listener to listen events from the CaptureModuleUI. */
168    private final CaptureModuleUI.CaptureModuleUIListener mUIListener =
169            new CaptureModuleUI.CaptureModuleUIListener() {
170                @Override
171                public void onZoomRatioChanged(float zoomRatio) {
172                    mZoomValue = zoomRatio;
173                    if (mCamera != null) {
174                        mCamera.setZoom(zoomRatio);
175                    }
176                }
177            };
178
179    /** The listener to respond preview area changes. */
180    private final PreviewStatusListener.PreviewAreaChangedListener mPreviewAreaChangedListener =
181            new PreviewStatusListener.PreviewAreaChangedListener() {
182                @Override
183                public void onPreviewAreaChanged(RectF previewArea) {
184                    mPreviewArea = previewArea;
185                    mFocusController.configurePreviewDimensions(previewArea);
186                }
187            };
188
189    /** The listener to listen events from the preview. */
190    private final PreviewStatusListener mPreviewStatusListener = new PreviewStatusListener() {
191        @Override
192        public void onPreviewLayoutChanged(View v, int left, int top, int right,
193                int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
194            int width = right - left;
195            int height = bottom - top;
196            updatePreviewTransform(width, height, false);
197        }
198
199        @Override
200        public boolean shouldAutoAdjustTransformMatrixOnLayout() {
201            return USE_AUTOTRANSFORM_UI_LAYOUT;
202        }
203
204        @Override
205        public void onPreviewFlipped() {
206            // Do nothing because when preview is flipped, TextureView will lay
207            // itself out again, which will then trigger a transform matrix
208            // update.
209        }
210
211        @Override
212        public GestureDetector.OnGestureListener getGestureListener() {
213            return new GestureDetector.SimpleOnGestureListener() {
214                @Override
215                public boolean onSingleTapUp(MotionEvent ev) {
216                    Point tapPoint = new Point((int) ev.getX(), (int) ev.getY());
217                    Log.v(TAG, "onSingleTapUpPreview location=" + tapPoint);
218                    // TODO: This should query actual capability.
219                    if (mCameraFacing == Facing.FRONT) {
220                        return false;
221                    }
222                    startActiveFocusAt(tapPoint.x, tapPoint.y);
223                    return true;
224                }
225            };
226        }
227
228        @Override
229        public View.OnTouchListener getTouchListener() {
230            return null;
231        }
232
233        @Override
234        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
235            Log.d(TAG, "onSurfaceTextureAvailable");
236            // Force to re-apply transform matrix here as a workaround for
237            // b/11168275
238            updatePreviewTransform(width, height, true);
239            synchronized (mSurfaceTextureLock) {
240                mPreviewSurfaceTexture = surface;
241            }
242            reopenCamera();
243        }
244
245        @Override
246        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
247            Log.d(TAG, "onSurfaceTextureDestroyed");
248            synchronized (mSurfaceTextureLock) {
249                mPreviewSurfaceTexture = null;
250            }
251            closeCamera();
252            return true;
253        }
254
255        @Override
256        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
257            Log.d(TAG, "onSurfaceTextureSizeChanged");
258            updatePreviewBufferSize();
259        }
260
261        @Override
262        public void onSurfaceTextureUpdated(SurfaceTexture surface) {
263            if (mState == ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE) {
264                Log.d(TAG, "onSurfaceTextureUpdated --> updatePreviewTransform");
265                mState = ModuleState.IDLE;
266                CameraAppUI appUI = mAppController.getCameraAppUI();
267                updatePreviewTransform(appUI.getSurfaceWidth(), appUI.getSurfaceHeight(), true);
268            }
269        }
270    };
271
272    private final OneCamera.PictureSaverCallback mPictureSaverCallback =
273            new OneCamera.PictureSaverCallback() {
274                @Override
275                public void onRemoteThumbnailAvailable(final byte[] jpegImage) {
276                    mMainThread.execute(new Runnable() {
277                        @Override
278                        public void run() {
279                            mAppController.getServices().getRemoteShutterListener()
280                                    .onPictureTaken(jpegImage);
281                        }
282                    });
283                }
284            };
285
286    /** State by the module state machine. */
287    private static enum ModuleState {
288        IDLE,
289        WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED,
290        UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE,
291    }
292
293    /** The current state of the module. */
294    private ModuleState mState = ModuleState.IDLE;
295    /** Current zoom value. */
296    private float mZoomValue = 1f;
297
298    /** Records beginning frame of each AF scan. */
299    private long mAutoFocusScanStartFrame = -1;
300    /** Records beginning time of each AF scan in uptimeMillis. */
301    private long mAutoFocusScanStartTime;
302
303    /** Heading sensor. */
304    private HeadingSensor mHeadingSensor;
305
306    /** Used to fetch and embed the location into captured images. */
307    private final LocationManager mLocationManager;
308    /** Plays sounds for countdown timer. */
309    private SoundPlayer mSoundPlayer;
310    private final MediaActionSound mMediaActionSound;
311
312    /** Whether the module is paused right now. */
313    private boolean mPaused;
314
315    /** Main thread. */
316    private final MainThread mMainThread;
317    /** Handler thread for camera-related operations. */
318    private Handler mCameraHandler;
319
320    /** Current display rotation in degrees. */
321    private int mDisplayRotation;
322    /** Current screen width in pixels. */
323    private int mScreenWidth;
324    /** Current screen height in pixels. */
325    private int mScreenHeight;
326    /** Current width of preview frames from camera. */
327    private int mPreviewBufferWidth;
328    /** Current height of preview frames from camera.. */
329    private int mPreviewBufferHeight;
330    /** Area used by preview. */
331    RectF mPreviewArea;
332
333    /** The surface texture for the preview. */
334    private SurfaceTexture mPreviewSurfaceTexture;
335
336    /** The burst manager for controlling the burst. */
337    private final BurstFacade mBurstController;
338    private static final String BURST_SESSIONS_DIR = "burst_sessions";
339
340    private final Profiler mProfiler = Profilers.instance().guard();
341
342    public CaptureModule(AppController appController) {
343        this(appController, false);
344    }
345
346    /** Constructs a new capture module. */
347    public CaptureModule(AppController appController, boolean stickyHdr) {
348        super(appController);
349        Profile guard = mProfiler.create("new CaptureModule").start();
350        mPaused = true;
351        mMainThread = MainThread.create();
352        mAppController = appController;
353        mContext = mAppController.getAndroidContext();
354        mSettingsManager = mAppController.getSettingsManager();
355        mStickyGcamCamera = stickyHdr;
356        mLocationManager = mAppController.getLocationManager();
357        mPreviewTransformCalculator = new PreviewTransformCalculator(
358                mAppController.getOrientationManager());
359
360        mBurstController = BurstFacadeFactory.create(mContext,
361                new OrientationLockController() {
362                    @Override
363                    public void unlockOrientation() {
364                        mAppController.getOrientationManager().unlockOrientation();
365                    }
366
367                        @Override
368                    public void lockOrientation() {
369                        mAppController.getOrientationManager().lockOrientation();
370                    }
371                },
372                new BurstReadyStateChangeListener() {
373                   @Override
374                    public void onBurstReadyStateChanged(boolean ready) {
375                        // TODO: This needs to take into account the state of
376                        // the whole system, not just burst.
377                       onReadyStateChanged(false);
378                    }
379                });
380        mMediaActionSound = new MediaActionSound();
381        guard.stop();
382    }
383
384    private boolean updateCameraCharacteristics() {
385        try {
386            CameraId cameraId = mOneCameraManager.findFirstCameraFacing(mCameraFacing);
387            if (cameraId != null && cameraId.getValue() != null) {
388                mCameraCharacteristics = mOneCameraManager.getOneCameraCharacteristics(cameraId);
389                return mCameraCharacteristics != null;
390            }
391        } catch (OneCameraAccessException ignored) { }
392            mAppController.getFatalErrorHandler().onGenericCameraAccessFailure();
393            return false;
394    }
395
396    @Override
397    public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
398        Profile guard = mProfiler.create("CaptureModule.init").start();
399        Log.d(TAG, "init UseAutotransformUiLayout = " + USE_AUTOTRANSFORM_UI_LAYOUT);
400        HandlerThread thread = new HandlerThread("CaptureModule.mCameraHandler");
401        thread.start();
402        mCameraHandler = new Handler(thread.getLooper());
403        mOneCameraOpener = mAppController.getCameraOpener();
404
405        try {
406            mOneCameraManager = OneCameraModule.provideOneCameraManager();
407        } catch (OneCameraException e) {
408            Log.e(TAG, "Unable to provide a OneCameraManager. ", e);
409        }
410        mDisplayRotation = CameraUtil.getDisplayRotation();
411        mCameraFacing = getFacingFromCameraId(
412              mSettingsManager.getInteger(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID));
413        mShowErrorAndFinish = !updateCameraCharacteristics();
414        if (mShowErrorAndFinish) {
415            return;
416        }
417        mUI = new CaptureModuleUI(activity, mAppController.getModuleLayoutRoot(), mUIListener);
418        mAppController.setPreviewStatusListener(mPreviewStatusListener);
419        synchronized (mSurfaceTextureLock) {
420            mPreviewSurfaceTexture = mAppController.getCameraAppUI().getSurfaceTexture();
421        }
422        mSoundPlayer = new SoundPlayer(mContext);
423
424        FocusSound focusSound = new FocusSound(mSoundPlayer, R.raw.material_camera_focus);
425        mFocusController = new FocusController(mUI.getFocusRing(), focusSound, mMainThread);
426
427        mHeadingSensor = new HeadingSensor(AndroidServices.instance().provideSensorManager());
428
429        View cancelButton = activity.findViewById(R.id.shutter_cancel_button);
430        cancelButton.setOnClickListener(new View.OnClickListener() {
431            @Override
432            public void onClick(View view) {
433                cancelCountDown();
434            }
435        });
436
437        mMediaActionSound.load(MediaActionSound.SHUTTER_CLICK);
438        guard.stop();
439    }
440
441    @Override
442    public void onShutterButtonLongPressed() {
443        try {
444            OneCameraCharacteristics cameraCharacteristics;
445            CameraId cameraId = mOneCameraManager.findFirstCameraFacing(mCameraFacing);
446            cameraCharacteristics = mOneCameraManager.getOneCameraCharacteristics(cameraId);
447            DeviceOrientation deviceOrientation = mAppController.getOrientationManager()
448                    .getDeviceOrientation();
449            ImageRotationCalculator imageRotationCalculator = ImageRotationCalculatorImpl
450                    .from(mAppController.getOrientationManager(), cameraCharacteristics);
451
452            mBurstController.startBurst(
453                    new CaptureSession.CaptureSessionCreator() {
454                        @Override
455                        public CaptureSession createAndStartEmpty() {
456                            return createAndStartCaptureSession();
457                        }
458                    },
459                    deviceOrientation,
460                    mCamera.getDirection(),
461                    imageRotationCalculator.toImageRotation().getDegrees());
462
463        } catch (OneCameraAccessException e) {
464            Log.e(TAG, "Cannot start burst", e);
465            return;
466        }
467    }
468
469    @Override
470    public void onShutterButtonFocus(boolean pressed) {
471        if (!pressed) {
472            // the shutter button was released, stop any bursts.
473            mBurstController.stopBurst();
474        }
475    }
476
477    @Override
478    public void onShutterCoordinate(TouchCoordinate coord) {
479        mLastShutterTouchCoordinate = coord;
480    }
481
482    @Override
483    public void onShutterButtonClick() {
484        if (mCamera == null) {
485            return;
486        }
487
488        int countDownDuration = mSettingsManager
489                .getInteger(SettingsManager.SCOPE_GLOBAL, Keys.KEY_COUNTDOWN_DURATION);
490        if (countDownDuration > 0) {
491            // Start count down.
492            mAppController.getCameraAppUI().transitionToCancel();
493            mAppController.getCameraAppUI().hideModeOptions();
494            mUI.setCountdownFinishedListener(this);
495            mUI.startCountdown(countDownDuration);
496            // Will take picture later via listener callback.
497        } else {
498            takePictureNow();
499        }
500    }
501
502
503    private void decorateSessionAtCaptureTime(CaptureSession session) {
504        String flashSetting =
505                mSettingsManager.getString(mAppController.getCameraScope(),
506                        Keys.KEY_FLASH_MODE);
507        boolean gridLinesOn = Keys.areGridLinesOn(mSettingsManager);
508        float timerDuration = mSettingsManager
509                .getInteger(SettingsManager.SCOPE_GLOBAL, Keys.KEY_COUNTDOWN_DURATION);
510
511        session.getCollector().decorateAtTimeCaptureRequest(
512                eventprotos.NavigationChange.Mode.PHOTO_CAPTURE,
513                session.getTitle() + ".jpg",
514                (mCameraFacing == Facing.FRONT),
515                mHdrSceneEnabled,
516                mZoomValue,
517                flashSetting,
518                gridLinesOn,
519                timerDuration,
520                mLastShutterTouchCoordinate,
521                null /* TODO: Implement Volume Button Shutter Click Instrumentation */,
522                mCameraCharacteristics.getSensorInfoActiveArraySize()
523        );
524    }
525
526    private void takePictureNow() {
527        if (mCamera == null) {
528            Log.i(TAG, "Not taking picture since Camera is closed.");
529            return;
530        }
531
532        CaptureSession session = createAndStartCaptureSession();
533        int orientation = mAppController.getOrientationManager().getDeviceOrientation()
534                .getDegrees();
535
536        // TODO: This should really not use getExternalCacheDir and instead use
537        // the SessionStorage API. Need to sync with gcam if that's OK.
538        PhotoCaptureParameters params = new PhotoCaptureParameters(
539                session.getTitle(), orientation, session.getLocation(),
540                mContext.getExternalCacheDir(), this, mPictureSaverCallback,
541                mHeadingSensor.getCurrentHeading(), mZoomValue, 0);
542        decorateSessionAtCaptureTime(session);
543        mCamera.takePicture(params, session);
544    }
545
546    /**
547     * Creates, starts and returns a new capture session. The returned session
548     * will have been started with an empty placeholder image.
549     */
550    private CaptureSession createAndStartCaptureSession() {
551        long sessionTime = getSessionTime();
552        Location location = mLocationManager.getCurrentLocation();
553        String title = CameraUtil.instance().createJpegName(sessionTime);
554        CaptureSession session = getServices().getCaptureSessionManager()
555                .createNewSession(title, sessionTime, location);
556        session.startEmpty(new Size((int) mPreviewArea.width(), (int) mPreviewArea.height()));
557        return session;
558    }
559
560    private long getSessionTime() {
561        // TODO: Replace with a mockable TimeProvider interface.
562        return System.currentTimeMillis();
563    }
564
565    @Override
566    public void onCountDownFinished() {
567        mAppController.getCameraAppUI().transitionToCapture();
568        mAppController.getCameraAppUI().showModeOptions();
569        if (mPaused) {
570            return;
571        }
572        takePictureNow();
573    }
574
575    @Override
576    public void onRemainingSecondsChanged(int remainingSeconds) {
577        if (remainingSeconds == 1) {
578            mSoundPlayer.play(R.raw.timer_final_second, 0.6f);
579        } else if (remainingSeconds == 2 || remainingSeconds == 3) {
580            mSoundPlayer.play(R.raw.timer_increment, 0.6f);
581        }
582    }
583
584    private void cancelCountDown() {
585        if (mUI.isCountingDown()) {
586            // Cancel on-going countdown.
587            mUI.cancelCountDown();
588        }
589
590        if (!mPaused) {
591            mAppController.getCameraAppUI().showModeOptions();
592            mAppController.getCameraAppUI().transitionToCapture();
593        }
594    }
595
596    @Override
597    public void onQuickExpose() {
598        mMainThread.execute(new Runnable() {
599            @Override
600            public void run() {
601                // Starts the short version of the capture animation UI.
602                mAppController.startFlashAnimation(true);
603                mMediaActionSound.play(MediaActionSound.SHUTTER_CLICK);
604            }
605        });
606    }
607
608    @Override
609    public void onRemoteShutterPress() {
610        Log.d(TAG, "onRemoteShutterPress");
611        // TODO: Check whether shutter is enabled.
612        takePictureNow();
613    }
614
615    private void initSurfaceTextureConsumer() {
616        synchronized (mSurfaceTextureLock) {
617            if (mPreviewSurfaceTexture != null) {
618                mPreviewSurfaceTexture.setDefaultBufferSize(
619                        mAppController.getCameraAppUI().getSurfaceWidth(),
620                        mAppController.getCameraAppUI().getSurfaceHeight());
621            }
622        }
623        reopenCamera();
624    }
625
626    private void reopenCamera() {
627        if (mPaused) {
628            return;
629        }
630        closeCamera();
631        openCameraAndStartPreview();
632    }
633
634    private SurfaceTexture getPreviewSurfaceTexture() {
635        synchronized (mSurfaceTextureLock) {
636            return mPreviewSurfaceTexture;
637        }
638    }
639
640    private void updatePreviewBufferSize() {
641        synchronized (mSurfaceTextureLock) {
642            if (mPreviewSurfaceTexture != null) {
643                mPreviewSurfaceTexture.setDefaultBufferSize(mPreviewBufferWidth,
644                        mPreviewBufferHeight);
645            }
646        }
647    }
648
649    @Override
650    public void resume() {
651        if (mShowErrorAndFinish) {
652            return;
653        }
654        Profile guard = mProfiler.create("CaptureModule.resume").start();
655
656        // We'll transition into 'ready' once the preview is started.
657        onReadyStateChanged(false);
658        mPaused = false;
659        mAppController.addPreviewAreaSizeChangedListener(mPreviewAreaChangedListener);
660        mAppController.addPreviewAreaSizeChangedListener(mUI);
661
662        guard.mark();
663        getServices().getRemoteShutterListener().onModuleReady(this);
664        guard.mark("getRemoteShutterListener.onModuleReady");
665        mBurstController.initialize(new SurfaceTexture(0));
666
667        // TODO: Check if we can really take a photo right now (memory, camera
668        // state, ... ).
669        mAppController.getCameraAppUI().enableModeOptions();
670        mAppController.setShutterEnabled(true);
671        mAppController.getCameraAppUI().showAccessibilityZoomUI(
672                mCameraCharacteristics.getAvailableMaxDigitalZoom());
673
674        mHdrPlusEnabled = mStickyGcamCamera || mAppController.getSettingsManager().getInteger(
675                SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS) == 1;
676
677        mHdrSceneEnabled = !mStickyGcamCamera && mAppController.getSettingsManager().getBoolean(
678              SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR);
679
680        // The lock only exists for HDR and causes trouble for non-HDR
681        // OneCameras.
682        // TODO: Fix for removing the locks completely is tracked at b/17985028
683        if (!mHdrPlusEnabled) {
684            mCameraOpenCloseLock.release();
685        }
686
687        // This means we are resuming with an existing preview texture. This
688        // means we will never get the onSurfaceTextureAvailable call. So we
689        // have to open the camera and start the preview here.
690        SurfaceTexture texture = getPreviewSurfaceTexture();
691
692        guard.mark();
693        if (texture != null) {
694            initSurfaceTextureConsumer();
695            guard.mark("initSurfaceTextureConsumer");
696        }
697
698        mSoundPlayer.loadSound(R.raw.timer_final_second);
699        mSoundPlayer.loadSound(R.raw.timer_increment);
700
701        guard.mark();
702        mHeadingSensor.activate();
703        guard.stop("mHeadingSensor.activate()");
704    }
705
706    @Override
707    public void pause() {
708        if (mShowErrorAndFinish) {
709            return;
710        }
711        mPaused = true;
712        mHeadingSensor.deactivate();
713
714        mAppController.removePreviewAreaSizeChangedListener(mUI);
715        mAppController.removePreviewAreaSizeChangedListener(mPreviewAreaChangedListener);
716        getServices().getRemoteShutterListener().onModuleExit();
717        mBurstController.release();
718        cancelCountDown();
719        closeCamera();
720        resetTextureBufferSize();
721        mSoundPlayer.unloadSound(R.raw.timer_final_second);
722        mSoundPlayer.unloadSound(R.raw.timer_increment);
723    }
724
725    @Override
726    public void destroy() {
727        mSoundPlayer.release();
728        mMediaActionSound.release();
729        mCameraHandler.getLooper().quitSafely();
730    }
731
732    @Override
733    public void onLayoutOrientationChanged(boolean isLandscape) {
734        Log.d(TAG, "onLayoutOrientationChanged");
735    }
736
737    @Override
738    public void onCameraAvailable(CameraProxy cameraProxy) {
739        // Ignore since we manage the camera ourselves until we remove this.
740    }
741
742    @Override
743    public void hardResetSettings(SettingsManager settingsManager) {
744        if (mStickyGcamCamera) {
745            // Sticky HDR+ mode should hard reset HDR+ to on, and camera back
746            // facing.
747            settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, true);
748            settingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID,
749                  mOneCameraManager.findFirstCameraFacing(Facing.BACK).getValue());
750        }
751    }
752
753    @Override
754    public HardwareSpec getHardwareSpec() {
755        return new HardwareSpec() {
756            @Override
757            public boolean isFrontCameraSupported() {
758                return true;
759            }
760
761            @Override
762            public boolean isHdrSupported() {
763                if (ApiHelper.IS_NEXUS_4 && is16by9AspectRatio(mPictureSize)) {
764                    Log.v(TAG, "16:9 N4, no HDR support");
765                    return false;
766                } else {
767                    return mCameraCharacteristics.isHdrSceneSupported();
768                }
769            }
770
771            @Override
772            public boolean isHdrPlusSupported() {
773                OneCameraFeatureConfig featureConfig = mAppController.getCameraFeatureConfig();
774                return featureConfig.getHdrPlusSupportLevel(mCameraFacing) !=
775                        OneCameraFeatureConfig.HdrPlusSupportLevel.NONE;
776            }
777
778            @Override
779            public boolean isFlashSupported() {
780                return mCameraCharacteristics.isFlashSupported();
781            }
782        };
783    }
784
785    @Override
786    public BottomBarUISpec getBottomBarSpec() {
787        HardwareSpec hardwareSpec = getHardwareSpec();
788        BottomBarUISpec bottomBarSpec = new BottomBarUISpec();
789        bottomBarSpec.enableGridLines = true;
790        bottomBarSpec.enableCamera = true;
791        bottomBarSpec.cameraCallback = getCameraCallback();
792        bottomBarSpec.enableHdr =
793                hardwareSpec.isHdrSupported() || hardwareSpec.isHdrPlusSupported();
794        bottomBarSpec.hdrCallback = getHdrButtonCallback();
795        bottomBarSpec.enableSelfTimer = true;
796        bottomBarSpec.showSelfTimer = true;
797        bottomBarSpec.isExposureCompensationSupported = mCameraCharacteristics
798                .isExposureCompensationSupported();
799        bottomBarSpec.enableExposureCompensation = bottomBarSpec.isExposureCompensationSupported;
800
801        // We must read the key from the settings because the button callback
802        // is not executed until after this method is called.
803        if ((hardwareSpec.isHdrPlusSupported() &&
804                mAppController.getSettingsManager().getBoolean(
805                SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS)) ||
806              ( hardwareSpec.isHdrSupported() &&
807                mAppController.getSettingsManager().getBoolean(
808                SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR))) {
809            // Disable flash if this is a sticky gcam camera, or if
810            // HDR is enabled.
811            bottomBarSpec.enableFlash = false;
812            // Disable manual exposure if HDR is enabled.
813            bottomBarSpec.enableExposureCompensation = false;
814        } else {
815            // If we are not in HDR / GCAM mode, fallback on the
816            // flash supported property and manual exposure supported property
817            // for this camera.
818            bottomBarSpec.enableFlash = mCameraCharacteristics.isFlashSupported();
819        }
820
821        bottomBarSpec.minExposureCompensation =
822                mCameraCharacteristics.getMinExposureCompensation();
823        bottomBarSpec.maxExposureCompensation =
824                mCameraCharacteristics.getMaxExposureCompensation();
825        bottomBarSpec.exposureCompensationStep =
826                mCameraCharacteristics.getExposureCompensationStep();
827        bottomBarSpec.exposureCompensationSetCallback =
828                new BottomBarUISpec.ExposureCompensationSetCallback() {
829                    @Override
830                    public void setExposure(int value) {
831                        mSettingsManager.set(
832                                mAppController.getCameraScope(), Keys.KEY_EXPOSURE, value);
833                    }
834                };
835
836        Log.v(TAG, ">>>>>>>> bottomBarSpec.enableHdr=" + bottomBarSpec.enableHdr);
837        return bottomBarSpec;
838    }
839
840    @Override
841    public boolean isUsingBottomBar() {
842        return true;
843    }
844
845    @Override
846    public boolean onKeyDown(int keyCode, KeyEvent event) {
847        switch (keyCode) {
848            case KeyEvent.KEYCODE_CAMERA:
849            case KeyEvent.KEYCODE_DPAD_CENTER:
850                if (mUI.isCountingDown()) {
851                    cancelCountDown();
852                } else if (event.getRepeatCount() == 0) {
853                    onShutterButtonClick();
854                }
855                return true;
856            case KeyEvent.KEYCODE_VOLUME_UP:
857            case KeyEvent.KEYCODE_VOLUME_DOWN:
858                // Prevent default.
859                return true;
860        }
861        return false;
862    }
863
864    @Override
865    public boolean onKeyUp(int keyCode, KeyEvent event) {
866        switch (keyCode) {
867            case KeyEvent.KEYCODE_VOLUME_UP:
868            case KeyEvent.KEYCODE_VOLUME_DOWN:
869                onShutterButtonClick();
870                return true;
871        }
872        return false;
873    }
874
875    // TODO: Consider refactoring FocusOverlayManager.
876    // Currently AF state transitions are controlled in OneCameraImpl.
877    // PhotoModule uses FocusOverlayManager which uses API1/portability
878    // logic and coordinates.
879    private void startActiveFocusAt(int viewX, int viewY) {
880        if (mCamera == null) {
881            // If we receive this after the camera is closed, do nothing.
882            return;
883        }
884
885        // TODO: make mFocusController final and remove null check.
886        if (mFocusController == null) {
887            Log.v(TAG, "CaptureModule mFocusController is null!");
888            return;
889        }
890        mFocusController.showActiveFocusAt(viewX, viewY);
891
892        // Normalize coordinates to [0,1] per CameraOne API.
893        float points[] = new float[2];
894        points[0] = (viewX - mPreviewArea.left) / mPreviewArea.width();
895        points[1] = (viewY - mPreviewArea.top) / mPreviewArea.height();
896
897        // Rotate coordinates to portrait orientation per CameraOne API.
898        Matrix rotationMatrix = new Matrix();
899        rotationMatrix.setRotate(mDisplayRotation, 0.5f, 0.5f);
900        rotationMatrix.mapPoints(points);
901        mCamera.triggerFocusAndMeterAtPoint(points[0], points[1]);
902
903        // Log touch (screen coordinates).
904        if (mZoomValue == 1f) {
905            TouchCoordinate touchCoordinate = new TouchCoordinate(
906                    viewX - mPreviewArea.left,
907                    viewY - mPreviewArea.top,
908                    mPreviewArea.width(),
909                    mPreviewArea.height());
910            // TODO: Add to logging: duration, rotation.
911            UsageStatistics.instance().tapToFocus(touchCoordinate, null);
912        }
913    }
914
915    /**
916     * Show AF target in center of preview.
917     */
918    private void startPassiveFocus() {
919        // TODO: make mFocusController final and remove null check.
920        if (mFocusController == null) {
921            return;
922        }
923
924        // TODO: Some passive focus scans may trigger on a location
925        // instead of the center of the screen.
926        mFocusController.showPassiveFocusAtCenter();
927    }
928
929    /**
930     * Update UI based on AF state changes.
931     */
932    @Override
933    public void onFocusStatusUpdate(final AutoFocusState state, long frameNumber) {
934        Log.v(TAG, "AF status is state:" + state);
935
936        switch (state) {
937            case PASSIVE_SCAN:
938                startPassiveFocus();
939                break;
940            case ACTIVE_SCAN:
941                // Unused, manual scans are triggered via the UI
942                break;
943            case PASSIVE_FOCUSED:
944            case PASSIVE_UNFOCUSED:
945                // Unused
946                break;
947            case ACTIVE_FOCUSED:
948            case ACTIVE_UNFOCUSED:
949                // Unused
950                break;
951        }
952
953        if (CAPTURE_DEBUG_UI) {
954            measureAutoFocusScans(state, frameNumber);
955        }
956    }
957
958    private void measureAutoFocusScans(final AutoFocusState state, long frameNumber) {
959        // Log AF scan lengths.
960        boolean passive = false;
961        switch (state) {
962            case PASSIVE_SCAN:
963            case ACTIVE_SCAN:
964                if (mAutoFocusScanStartFrame == -1) {
965                    mAutoFocusScanStartFrame = frameNumber;
966                    mAutoFocusScanStartTime = SystemClock.uptimeMillis();
967                }
968                break;
969            case PASSIVE_FOCUSED:
970            case PASSIVE_UNFOCUSED:
971                passive = true;
972            case ACTIVE_FOCUSED:
973            case ACTIVE_UNFOCUSED:
974                if (mAutoFocusScanStartFrame != -1) {
975                    long frames = frameNumber - mAutoFocusScanStartFrame;
976                    long dt = SystemClock.uptimeMillis() - mAutoFocusScanStartTime;
977                    int fps = Math.round(frames * 1000f / dt);
978                    String report = String.format("%s scan: fps=%d frames=%d",
979                            passive ? "CAF" : "AF", fps, frames);
980                    Log.v(TAG, report);
981                    mUI.showDebugMessage(String.format("%d / %d", frames, fps));
982                    mAutoFocusScanStartFrame = -1;
983                }
984                break;
985        }
986    }
987
988    @Override
989    public void onReadyStateChanged(boolean readyForCapture) {
990        if (readyForCapture) {
991            mAppController.getCameraAppUI().enableModeOptions();
992        }
993        mAppController.setShutterEnabled(readyForCapture);
994    }
995
996    @Override
997    public String getPeekAccessibilityString() {
998        return mAppController.getAndroidContext()
999                .getResources().getString(R.string.photo_accessibility_peek);
1000    }
1001
1002    @Override
1003    public void onThumbnailResult(byte[] jpegData) {
1004        getServices().getRemoteShutterListener().onPictureTaken(jpegData);
1005    }
1006
1007    @Override
1008    public void onPictureTaken(CaptureSession session) {
1009        mAppController.getCameraAppUI().enableModeOptions();
1010    }
1011
1012    @Override
1013    public void onPictureSaved(Uri uri) {
1014        mAppController.notifyNewMedia(uri);
1015    }
1016
1017    @Override
1018    public void onTakePictureProgress(float progress) {
1019        mUI.setPictureTakingProgress((int) (progress * 100));
1020    }
1021
1022    @Override
1023    public void onPictureTakingFailed() {
1024        mAppController.getFatalErrorHandler().onMediaStorageFailure();
1025    }
1026
1027    /**
1028     * Updates the preview transform matrix to adapt to the current preview
1029     * width, height, and orientation.
1030     */
1031    public void updatePreviewTransform() {
1032        int width;
1033        int height;
1034        synchronized (mDimensionLock) {
1035            width = mScreenWidth;
1036            height = mScreenHeight;
1037        }
1038        updatePreviewTransform(width, height);
1039    }
1040
1041    /**
1042     * @return Depending on whether we're in sticky-HDR mode or not, return the
1043     *         proper callback to be used for when the HDR/HDR+ button is
1044     *         pressed.
1045     */
1046    private ButtonManager.ButtonCallback getHdrButtonCallback() {
1047        if (mStickyGcamCamera) {
1048            return new ButtonManager.ButtonCallback() {
1049                @Override
1050                public void onStateChanged(int state) {
1051                    if (mPaused) {
1052                        return;
1053                    }
1054                    if (state == ButtonManager.ON) {
1055                        throw new IllegalStateException(
1056                                "Can't leave hdr plus mode if switching to hdr plus mode.");
1057                    }
1058                    SettingsManager settingsManager = mAppController.getSettingsManager();
1059                    settingsManager.set(mAppController.getModuleScope(),
1060                            Keys.KEY_REQUEST_RETURN_HDR_PLUS, false);
1061                    switchToRegularCapture();
1062                }
1063            };
1064        } else {
1065            return new ButtonManager.ButtonCallback() {
1066                @Override
1067                public void onStateChanged(int hdrEnabled) {
1068                    if (mPaused) {
1069                        return;
1070                    }
1071
1072                    // Only reload the camera if we are toggling HDR+.
1073                    if (GcamHelper.hasGcamCapture(mAppController.getCameraFeatureConfig())) {
1074                        mHdrPlusEnabled = hdrEnabled == 1;
1075                        switchCamera();
1076                    } else {
1077                        mHdrSceneEnabled = hdrEnabled == 1;
1078                    }
1079                }
1080            };
1081        }
1082    }
1083
1084    /**
1085     * @return Depending on whether we're in sticky-HDR mode or not, this
1086     *         returns the proper callback to be used for when the camera
1087     *         (front/back switch) button is pressed.
1088     */
1089    private ButtonManager.ButtonCallback getCameraCallback() {
1090        if (mStickyGcamCamera) {
1091            return new ButtonManager.ButtonCallback() {
1092                @Override
1093                public void onStateChanged(int state) {
1094                    if (mPaused) {
1095                        return;
1096                    }
1097
1098                    // At the time this callback is fired, the camera id setting
1099                    // has changed to the desired camera.
1100                    SettingsManager settingsManager = mAppController.getSettingsManager();
1101                    if (Keys.isCameraBackFacing(settingsManager,
1102                            mAppController.getModuleScope())) {
1103                        throw new IllegalStateException(
1104                                "Hdr plus should never be switching from front facing camera.");
1105                    }
1106
1107                    // Switch to photo mode, but request a return to hdr plus on
1108                    // switching to back camera again.
1109                    settingsManager.set(mAppController.getModuleScope(),
1110                            Keys.KEY_REQUEST_RETURN_HDR_PLUS, true);
1111                    switchToRegularCapture();
1112                }
1113            };
1114        } else {
1115            return new ButtonManager.ButtonCallback() {
1116                @Override
1117                public void onStateChanged(int cameraId) {
1118                    if (mPaused) {
1119                        return;
1120                    }
1121
1122                    // At the time this callback is fired, the camera id
1123                    // has be set to the desired camera.
1124                    mSettingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID,
1125                            cameraId);
1126
1127                    Log.d(TAG, "Start to switch camera. cameraId=" + cameraId);
1128                    mCameraFacing = getFacingFromCameraId(cameraId);
1129                    mShowErrorAndFinish = !updateCameraCharacteristics();
1130                    switchCamera();
1131                }
1132            };
1133        }
1134    }
1135
1136    /**
1137     * Switches to PhotoModule to do regular photo captures.
1138     * <p>
1139     * TODO: Remove this once we use CaptureModule for photo taking.
1140     */
1141    private void switchToRegularCapture() {
1142        // Turn off HDR+ before switching back to normal photo mode.
1143        SettingsManager settingsManager = mAppController.getSettingsManager();
1144        settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, false);
1145
1146        // Disable this button to prevent callbacks from this module from firing
1147        // while we are transitioning modules.
1148        ButtonManager buttonManager = mAppController.getButtonManager();
1149        buttonManager.disableButtonClick(ButtonManager.BUTTON_HDR_PLUS);
1150        mAppController.getCameraAppUI().freezeScreenUntilPreviewReady();
1151        mAppController.onModeSelected(mContext.getResources().getInteger(
1152                R.integer.camera_mode_photo));
1153        buttonManager.enableButtonClick(ButtonManager.BUTTON_HDR_PLUS);
1154    }
1155
1156    /**
1157     * Called when the preview started. Informs the app controller and queues a
1158     * transform update when the next preview frame arrives.
1159     */
1160    private void onPreviewStarted() {
1161        if (mState == ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED) {
1162            mState = ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE;
1163        }
1164        mAppController.onPreviewStarted();
1165    }
1166
1167    /**
1168     * Update the preview transform based on the new dimensions. Will not force
1169     * an update, if it's not necessary.
1170     */
1171    private void updatePreviewTransform(int incomingWidth, int incomingHeight) {
1172        updatePreviewTransform(incomingWidth, incomingHeight, false);
1173    }
1174
1175    /**
1176     * Returns whether it is necessary to apply device-specific fix for b/19271661
1177     * on the AutoTransform Path, i.e. USE_AUTOTRANSFORM_UI_LAYOUT == true
1178     *
1179     * @return whether to apply workaround fix for b/19271661
1180     */
1181    private boolean requiresNexus4SpecificFixFor16By9Previews() {
1182        return USE_AUTOTRANSFORM_UI_LAYOUT && ApiHelper.IS_NEXUS_4
1183                && is16by9AspectRatio(mPictureSize);
1184    }
1185
1186    /***
1187     * Update the preview transform based on the new dimensions. TODO: Make work
1188     * with all: aspect ratios/resolutions x screens/cameras.
1189     */
1190    private void updatePreviewTransform(int incomingWidth, int incomingHeight,
1191            boolean forceUpdate) {
1192        Log.d(TAG, "updatePreviewTransform: " + incomingWidth + " x " + incomingHeight);
1193
1194        synchronized (mDimensionLock) {
1195            int incomingRotation = CameraUtil.getDisplayRotation();
1196            // Check for an actual change:
1197            if (mScreenHeight == incomingHeight && mScreenWidth == incomingWidth &&
1198                    incomingRotation == mDisplayRotation && !forceUpdate) {
1199                return;
1200            }
1201            // Update display rotation and dimensions
1202            mDisplayRotation = incomingRotation;
1203            mScreenWidth = incomingWidth;
1204            mScreenHeight = incomingHeight;
1205            updatePreviewBufferDimension();
1206
1207            // Assumptions:
1208            // - Aspect ratio for the sensor buffers is in landscape
1209            // orientation,
1210            // - Dimensions of buffers received are rotated to the natural
1211            // device orientation.
1212            // - The contents of each buffer are rotated by the inverse of
1213            // the display rotation.
1214            // - Surface scales the buffer to fit the current view bounds.
1215
1216            // Get natural orientation and buffer dimensions
1217
1218            if(USE_AUTOTRANSFORM_UI_LAYOUT) {
1219                // Use PhotoUI-based AutoTransformation Interface
1220                if (mPreviewBufferWidth != 0 && mPreviewBufferHeight != 0) {
1221                    if (requiresNexus4SpecificFixFor16By9Previews()) {
1222                        // Force preview size to be 16:9, even though surface is 4:3
1223                        // Surface content is assumed to be 16:9.
1224                        mAppController.updatePreviewAspectRatio(16.f / 9.f);
1225                    } else {
1226                        mAppController.updatePreviewAspectRatio(
1227                                mPreviewBufferWidth / (float) mPreviewBufferHeight);
1228                    }
1229                }
1230            } else {
1231                Matrix transformMatrix = mPreviewTransformCalculator.toTransformMatrix(
1232                        new Size(mScreenWidth, mScreenHeight),
1233                        new Size(mPreviewBufferWidth, mPreviewBufferHeight));
1234                mAppController.updatePreviewTransform(transformMatrix);
1235            }
1236        }
1237    }
1238
1239
1240    /**
1241     * Calculates whether a picture size is 16:9 ratio, regardless of its
1242     * orientation.
1243     *
1244     * @param size the size of the picture to be considered
1245     * @return true, if the picture is 16:9; false if it's invalid or size is null
1246     */
1247    private boolean is16by9AspectRatio(Size size) {
1248        if (size == null || size.getWidth() == 0 || size.getHeight() == 0) {
1249            return false;
1250        }
1251
1252        // Normalize aspect ratio to be greater than 1.
1253        final float aspectRatio = (size.getHeight() > size.getWidth())
1254                ? (size.getHeight() / (float) size.getWidth())
1255                : (size.getWidth() / (float) size.getHeight());
1256
1257        return Math.abs(aspectRatio - (16.f / 9.f)) < 0.001f;
1258    }
1259
1260    /**
1261     * Based on the current picture size, selects the best preview dimension and
1262     * stores it in {@link #mPreviewBufferWidth} and
1263     * {@link #mPreviewBufferHeight}.
1264     */
1265    private void updatePreviewBufferDimension() {
1266        if (mCamera == null) {
1267            return;
1268        }
1269
1270        Size previewBufferSize = mCamera.pickPreviewSize(mPictureSize, mContext);
1271        mPreviewBufferWidth = previewBufferSize.getWidth();
1272        mPreviewBufferHeight = previewBufferSize.getHeight();
1273
1274        // Workaround for N4 TextureView/HAL issues b/19271661 for 16:9 preview
1275        // streams.
1276        if (requiresNexus4SpecificFixFor16By9Previews()) {
1277            // Override the preview selection logic to the largest N4 4:3
1278            // preview size but pass in 16:9 aspect ratio in
1279            // UpdatePreviewAspectRatio later.
1280            mPreviewBufferWidth = 1280;
1281            mPreviewBufferHeight = 960;
1282        }
1283        updatePreviewBufferSize();
1284    }
1285
1286    /**
1287     * Open camera and start the preview.
1288     */
1289    private void openCameraAndStartPreview() {
1290        Profile guard = mProfiler.create("CaptureModule.openCameraAndStartPreview()").start();
1291        try {
1292            // TODO Given the current design, we cannot guarantee that one of
1293            // CaptureReadyCallback.onSetupFailed or onReadyForCapture will
1294            // be called (see below), so it's possible that
1295            // mCameraOpenCloseLock.release() is never called under extremely
1296            // rare cases. If we leak the lock, this timeout ensures that we at
1297            // least crash so we don't deadlock the app.
1298            if (!mCameraOpenCloseLock.tryAcquire(CAMERA_OPEN_CLOSE_TIMEOUT_MILLIS,
1299                    TimeUnit.MILLISECONDS)) {
1300                throw new RuntimeException("Time out waiting to acquire camera-open lock.");
1301            }
1302        } catch (InterruptedException e) {
1303            throw new RuntimeException("Interrupted while waiting to acquire camera-open lock.", e);
1304        }
1305
1306        guard.mark("Acquired mCameraOpenCloseLock");
1307
1308        if (mOneCameraOpener == null) {
1309            Log.e(TAG, "no available OneCameraManager, showing error dialog");
1310            mCameraOpenCloseLock.release();
1311            mAppController.getFatalErrorHandler().onGenericCameraAccessFailure();
1312            guard.stop("No OneCameraManager");
1313            return;
1314        }
1315        if (mCamera != null) {
1316            // If the camera is already open, do nothing.
1317            Log.d(TAG, "Camera already open, not re-opening.");
1318            mCameraOpenCloseLock.release();
1319            guard.stop("Camera is already open");
1320            return;
1321        }
1322
1323        // Derive objects necessary for camera creation.
1324        MainThread mainThread = MainThread.create();
1325        ImageRotationCalculator imageRotationCalculator = ImageRotationCalculatorImpl
1326                .from(mAppController.getOrientationManager(), mCameraCharacteristics);
1327
1328        // Only enable GCam on the back camera
1329        boolean useHdr = mHdrPlusEnabled && mCameraFacing == Facing.BACK;
1330
1331        CameraId cameraId = mOneCameraManager.findFirstCameraFacing(mCameraFacing);
1332        final String settingScope = SettingsManager.getCameraSettingScope(cameraId.getValue());
1333
1334        OneCameraCaptureSetting captureSetting;
1335        // Read the preferred picture size from the setting.
1336        try {
1337            mPictureSize = mAppController.getResolutionSetting().getPictureSize(
1338                    cameraId, mCameraFacing);
1339            captureSetting = OneCameraCaptureSetting.create(mPictureSize, mSettingsManager,
1340                    getHardwareSpec(), settingScope, useHdr);
1341        } catch (OneCameraAccessException ex) {
1342            mAppController.getFatalErrorHandler().onGenericCameraAccessFailure();
1343            return;
1344        }
1345
1346        mOneCameraOpener.open(cameraId, captureSetting, mCameraHandler, mainThread,
1347              imageRotationCalculator, mBurstController, mSoundPlayer,
1348              new OpenCallback() {
1349                  @Override
1350                  public void onFailure() {
1351                      Log.e(TAG, "Could not open camera.");
1352                      mCamera = null;
1353                      mCameraOpenCloseLock.release();
1354                      mAppController.getFatalErrorHandler().onCameraOpenFailure();
1355                  }
1356
1357                  @Override
1358                  public void onCameraClosed() {
1359                      mCamera = null;
1360                      mCameraOpenCloseLock.release();
1361                  }
1362
1363                  @Override
1364                  public void onCameraOpened(final OneCamera camera) {
1365                      Log.d(TAG, "onCameraOpened: " + camera);
1366                      mCamera = camera;
1367
1368                      // When camera is opened, the zoom is implicitly reset to 1.0f
1369                      mZoomValue = 1.0f;
1370
1371                      updatePreviewBufferDimension();
1372
1373                      // If the surface texture is not destroyed, it may have
1374                      // the last frame lingering. We need to hold off setting
1375                      // transform until preview is started.
1376                      updatePreviewBufferSize();
1377                      mState = ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED;
1378                      Log.d(TAG, "starting preview ...");
1379
1380                      // TODO: make mFocusController final and remove null
1381                      // check.
1382                      if (mFocusController != null) {
1383                          camera.setFocusDistanceListener(mFocusController);
1384                      }
1385
1386                      mMainThread.execute(new Runnable() {
1387                          @Override
1388                          public void run() {
1389                              mAppController.getCameraAppUI().onChangeCamera();
1390                          }
1391                      });
1392
1393                      // TODO: Consider rolling these two calls into one.
1394                      camera.startPreview(new Surface(getPreviewSurfaceTexture()),
1395                            new CaptureReadyCallback() {
1396                                @Override
1397                                public void onSetupFailed() {
1398                                    // We must release this lock here,
1399                                    // before posting to the main handler
1400                                    // since we may be blocked in pause(),
1401                                    // getting ready to close the camera.
1402                                    mCameraOpenCloseLock.release();
1403                                    Log.e(TAG, "Could not set up preview.");
1404                                    mMainThread.execute(new Runnable() {
1405                                        @Override
1406                                        public void run() {
1407                                            if (mCamera == null) {
1408                                                Log.d(TAG, "Camera closed, aborting.");
1409                                                return;
1410                                            }
1411                                            mCamera.close();
1412                                            mCamera = null;
1413                                            // TODO: Show an error message
1414                                            // and exit.
1415                                        }
1416                                    });
1417                                }
1418
1419                                @Override
1420                                public void onReadyForCapture() {
1421                                    // We must release this lock here,
1422                                    // before posting to the main handler
1423                                    // since we may be blocked in pause(),
1424                                    // getting ready to close the camera.
1425                                    mCameraOpenCloseLock.release();
1426                                    mMainThread.execute(new Runnable() {
1427                                        @Override
1428                                        public void run() {
1429                                            Log.d(TAG, "Ready for capture.");
1430                                            if (mCamera == null) {
1431                                                Log.d(TAG, "Camera closed, aborting.");
1432                                                return;
1433                                            }
1434                                            onPreviewStarted();
1435                                            // May be overridden by
1436                                            // subsequent call to
1437                                            // onReadyStateChanged().
1438                                            onReadyStateChanged(true);
1439                                            mCamera.setReadyStateChangedListener(
1440                                                  CaptureModule.this);
1441                                            // Enable zooming after preview
1442                                            // has started.
1443                                            mUI.initializeZoom(mCamera.getMaxZoom());
1444                                            mCamera.setFocusStateListener(CaptureModule.this);
1445                                        }
1446                                    });
1447                                }
1448                            });
1449                  }
1450              }, mAppController.getFatalErrorHandler());
1451        guard.stop("mOneCameraOpener.open()");
1452    }
1453
1454    private void closeCamera() {
1455        Profile profile = mProfiler.create("CaptureModule.closeCamera()").start();
1456        try {
1457            mCameraOpenCloseLock.acquire();
1458        } catch (InterruptedException e) {
1459            throw new RuntimeException("Interrupted while waiting to acquire camera-open lock.", e);
1460        }
1461        profile.mark("mCameraOpenCloseLock.acquire()");
1462        try {
1463            if (mCamera != null) {
1464                mCamera.close();
1465                profile.mark("mCamera.close()");
1466                mCamera.setFocusStateListener(null);
1467                mCamera = null;
1468            }
1469        } finally {
1470            mCameraOpenCloseLock.release();
1471        }
1472        profile.stop();
1473    }
1474
1475    /**
1476     * Re-initialize the camera if e.g. the HDR mode or facing property changed.
1477     */
1478    private void switchCamera() {
1479        if (mShowErrorAndFinish) {
1480            return;
1481        }
1482        if (mPaused) {
1483            return;
1484        }
1485        cancelCountDown();
1486        mAppController.freezeScreenUntilPreviewReady();
1487        initSurfaceTextureConsumer();
1488    }
1489
1490    /**
1491     * Returns which way around the camera is facing, based on it's ID.
1492     * <p>
1493     * TODO: This needs to change so that we store the direction directly in the
1494     * settings, rather than a Camera ID.
1495     */
1496    private static Facing getFacingFromCameraId(int cameraId) {
1497        return cameraId == 1 ? Facing.FRONT : Facing.BACK;
1498    }
1499
1500    private void resetTextureBufferSize() {
1501        // According to the documentation for
1502        // SurfaceTexture.setDefaultBufferSize,
1503        // photo and video based image producers (presumably only Camera 1 api),
1504        // override this buffer size. Any module that uses egl to render to a
1505        // SurfaceTexture must have these buffer sizes reset manually. Otherwise
1506        // the SurfaceTexture cannot be transformed by matrix set on the
1507        // TextureView.
1508        updatePreviewBufferSize();
1509    }
1510}
1511