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