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