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