blob: f3cb1df3a8edc6dbd61d24664b9e4d8fee4f404f [file] [log] [blame]
Sascha Haeberling08b3c942014-07-30 17:48:52 -07001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.camera;
18
19import android.app.Activity;
Sascha Haeberling08b3c942014-07-30 17:48:52 -070020import android.content.Context;
21import android.content.res.Configuration;
22import android.graphics.Bitmap;
23import android.graphics.Matrix;
24import android.graphics.RectF;
25import android.graphics.SurfaceTexture;
26import android.hardware.Sensor;
27import android.hardware.SensorEvent;
28import android.hardware.SensorEventListener;
29import android.hardware.SensorManager;
30import android.net.Uri;
31import android.os.Handler;
32import android.provider.MediaStore;
Sascha Haeberling08b3c942014-07-30 17:48:52 -070033import android.view.KeyEvent;
34import android.view.OrientationEventListener;
35import android.view.Surface;
36import android.view.TextureView;
37import android.view.View;
38import android.view.View.OnLayoutChangeListener;
39
40import com.android.camera.app.AppController;
41import com.android.camera.app.CameraAppUI;
42import com.android.camera.app.CameraAppUI.BottomBarUISpec;
43import com.android.camera.app.MediaSaver;
44import com.android.camera.debug.Log;
45import com.android.camera.debug.Log.Tag;
Sascha Haeberling08b3c942014-07-30 17:48:52 -070046import com.android.camera.hardware.HardwareSpec;
47import com.android.camera.module.ModuleController;
48import com.android.camera.one.OneCamera;
Andy Huibers30ffce02014-07-31 16:31:43 -070049import com.android.camera.one.OneCamera.AutoFocusMode;
50import com.android.camera.one.OneCamera.AutoFocusState;
Sascha Haeberling08b3c942014-07-30 17:48:52 -070051import com.android.camera.one.OneCamera.CaptureReadyCallback;
52import com.android.camera.one.OneCamera.Facing;
53import com.android.camera.one.OneCamera.OpenCallback;
54import com.android.camera.one.OneCamera.PhotoCaptureParameters;
Sascha Haeberling0cf4a022014-07-31 11:36:58 -070055import com.android.camera.one.OneCamera.PhotoCaptureParameters.Flash;
Sascha Haeberling08b3c942014-07-30 17:48:52 -070056import com.android.camera.one.OneCameraManager;
57import com.android.camera.remote.RemoteCameraModule;
58import com.android.camera.session.CaptureSession;
59import com.android.camera.settings.Keys;
60import com.android.camera.settings.ResolutionUtil;
61import com.android.camera.settings.SettingsManager;
62import com.android.camera.ui.PreviewStatusListener;
63import com.android.camera.ui.TouchCoordinate;
64import com.android.camera.util.CameraUtil;
Sascha Haeberlinge3dfd5a2014-08-05 15:54:42 -070065import com.android.camera.util.Size;
Andy Huibers30ffce02014-07-31 16:31:43 -070066import com.android.camera.util.SystemProperties;
67import com.android.camera.util.UsageStatistics;
Sascha Haeberling08b3c942014-07-30 17:48:52 -070068import com.android.camera2.R;
69import com.android.ex.camera2.portability.CameraAgent.CameraProxy;
70
Sascha Haeberling59c784b2014-08-05 10:53:08 -070071import java.io.File;
72
Sascha Haeberling08b3c942014-07-30 17:48:52 -070073/**
74 * New Capture module that is made to support photo and video capture on top of
75 * the OneCamera API, to transparently support GCam.
76 * <p>
77 * This has been a re-write with pieces taken and improved from GCamModule and
78 * PhotoModule, which are to be retired eventually.
79 * <p>
80 * TODO:
81 * <ul>
82 * <li>Server-side logging
83 * <li>Focusing
84 * <li>Show location dialog
85 * <li>Show resolution dialog on certain devices
86 * <li>Store location
87 * <li>Timer
Sascha Haeberling0cf4a022014-07-31 11:36:58 -070088 * <li>Capture intent
Sascha Haeberling08b3c942014-07-30 17:48:52 -070089 * </ul>
90 */
Sascha Haeberling0cf4a022014-07-31 11:36:58 -070091public class CaptureModule extends CameraModule
92 implements MediaSaver.QueueListener,
93 ModuleController,
94 OneCamera.PictureCallback,
Andy Huibers30ffce02014-07-31 16:31:43 -070095 OneCamera.FocusStateListener,
Sascha Haeberlinge3ad4352014-08-15 10:52:37 -070096 OneCamera.ReadyStateChangedListener,
Sascha Haeberling08b3c942014-07-30 17:48:52 -070097 PreviewStatusListener.PreviewAreaChangedListener,
Sascha Haeberling0cf4a022014-07-31 11:36:58 -070098 RemoteCameraModule,
99 SensorEventListener,
100 SettingsManager.OnSettingChangedListener,
101 TextureView.SurfaceTextureListener {
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700102
103 /**
104 * Called on layout changes.
105 */
106 private final OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() {
107 @Override
108 public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
109 int oldTop, int oldRight, int oldBottom) {
110 int width = right - left;
111 int height = bottom - top;
112 updatePreviewTransform(width, height, false);
113 }
114 };
115
116 /**
117 * Called when the captured media has been saved.
118 */
119 private final MediaSaver.OnMediaSavedListener mOnMediaSavedListener =
120 new MediaSaver.OnMediaSavedListener() {
121 @Override
122 public void onMediaSaved(Uri uri) {
123 if (uri != null) {
124 mAppController.notifyNewMedia(uri);
125 }
126 }
127 };
128
129 /**
130 * Called when the user pressed the back/front camera switch button.
131 */
132 private final ButtonManager.ButtonCallback mCameraSwitchCallback =
133 new ButtonManager.ButtonCallback() {
134 @Override
135 public void onStateChanged(int cameraId) {
136 // At the time this callback is fired, the camera id
137 // has be set to the desired camera.
138 if (mPaused) {
139 return;
140 }
141
142 mSettingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID,
143 cameraId);
144
145 Log.d(TAG, "Start to switch camera. cameraId=" + cameraId);
146 switchCamera(getFacingFromCameraId(cameraId));
147 }
148 };
149
Andy Huibers30ffce02014-07-31 16:31:43 -0700150 /**
151 * Show AF target in center of preview and start animation.
152 */
153 Runnable mShowAutoFocusTargetInCenterRunnable = new Runnable() {
154 @Override
155 public void run() {
156 mUI.setAutoFocusTarget(((int) (mPreviewArea.left + mPreviewArea.right)) / 2,
157 ((int) (mPreviewArea.top + mPreviewArea.bottom)) / 2);
158 mUI.showAutoFocusInProgress();
159 }
160 };
161
162 /**
163 * Hide AF target UI element.
164 */
165 Runnable mHideAutoFocusTargetRunnable = new Runnable() {
166 @Override
167 public void run() {
168 // showAutoFocusSuccess() just hides the AF UI.
169 mUI.showAutoFocusSuccess();
170 }
171 };
172
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700173 private static final Tag TAG = new Tag("CaptureModule");
174 private static final String PHOTO_MODULE_STRING_ID = "PhotoModule";
175 /** Enable additional debug output. */
176 private static final boolean DEBUG = true;
177 /**
178 * This is the delay before we execute onResume tasks when coming from the
179 * lock screen, to allow time for onPause to execute.
180 * <p>
181 * TODO: Make sure this value is in sync with what we see on L.
182 */
183 private static final int ON_RESUME_TASKS_DELAY_MSEC = 20;
184
Andy Huibers30ffce02014-07-31 16:31:43 -0700185 /** System Properties switch to enable debugging focus UI. */
186 private static final String PROP_FOCUS_DEBUG_UI_KEY = "persist.camera.focus_debug_ui";
187 private static final String PROP_FOCUS_DEBUG_UI_OFF = "0";
188 private static final boolean FOCUS_DEBUG_UI = !PROP_FOCUS_DEBUG_UI_OFF
189 .equals(SystemProperties.get(PROP_FOCUS_DEBUG_UI_KEY, PROP_FOCUS_DEBUG_UI_OFF));
190
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700191 private final Object mDimensionLock = new Object();
192 /**
193 * Lock for race conditions in the SurfaceTextureListener callbacks.
194 */
195 private final Object mSurfaceLock = new Object();
196 /** Controller giving us access to other services. */
197 private final AppController mAppController;
198 /** The applications settings manager. */
199 private final SettingsManager mSettingsManager;
200 /** Application context. */
201 private final Context mContext;
202 private CaptureModuleUI mUI;
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700203 /** The camera manager used to open cameras. */
204 private OneCameraManager mCameraManager;
205 /** The currently opened camera device. */
206 private OneCamera mCamera;
207 /** The direction the currently opened camera is facing to. */
208 private Facing mCameraFacing = Facing.BACK;
209 /** The texture used to render the preview in. */
210 private SurfaceTexture mPreviewTexture;
211
212 /** State by the module state machine. */
213 private static enum ModuleState {
214 IDLE,
215 WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED,
216 UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE,
217 }
218
219 /** The current state of the module. */
220 private ModuleState mState = ModuleState.IDLE;
221 /** Current orientation of the device. */
222 private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
Andy Huibers30ffce02014-07-31 16:31:43 -0700223 /** Current zoom value. */
Sascha Haeberling59c784b2014-08-05 10:53:08 -0700224 private final float mZoomValue = 1f;
Andy Huibers30ffce02014-07-31 16:31:43 -0700225
226 /** True if in AF tap-to-focus sequence. */
227 private boolean mTapToFocusInProgress = false;
228
229 /** Persistence of Tap to Focus target UI after scan complete. */
230 private static final int FOCUS_HOLD_UI_MILLIS = 500;
231 /** Persistence of Tap to Focus target UI timeout. */
232 private static final int FOCUS_HOLD_UI_TIMEOUT_MILLIS = 1500;
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700233
234 /** Accelerometer data. */
235 private final float[] mGData = new float[3];
236 /** Magnetic sensor data. */
237 private final float[] mMData = new float[3];
238 /** Temporary rotation matrix. */
239 private final float[] mR = new float[16];
240 /** Current compass heading. */
241 private int mHeading = -1;
242
243 /** Whether the module is paused right now. */
244 private boolean mPaused;
245
246 /** Whether this module was resumed from lockscreen capture intent. */
247 private boolean mIsResumeFromLockScreen = false;
248
249 private final Runnable mResumeTaskRunnable = new Runnable() {
250 @Override
251 public void run() {
252 onResumeTasks();
253 }
254 };
255
256 /** Main thread handler. */
257 private Handler mMainHandler;
258
259 /** Current display rotation in degrees. */
260 private int mDisplayRotation;
Andy Huibers30ffce02014-07-31 16:31:43 -0700261 /** Current screen width in pixels. */
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700262 private int mScreenWidth;
Andy Huibers30ffce02014-07-31 16:31:43 -0700263 /** Current screen height in pixels. */
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700264 private int mScreenHeight;
Andy Huibers30ffce02014-07-31 16:31:43 -0700265 /** Current width of preview frames from camera. */
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700266 private int mPreviewBufferWidth;
Andy Huibers30ffce02014-07-31 16:31:43 -0700267 /** Current height of preview frames from camera.. */
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700268 private int mPreviewBufferHeight;
Andy Huibers30ffce02014-07-31 16:31:43 -0700269 /** Area used by preview. */
270 RectF mPreviewArea;
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700271
272 /** The current preview transformation matrix. */
273 private Matrix mPreviewTranformationMatrix = new Matrix();
274 /** TODO: This is N5 specific. */
275 public static final float FULLSCREEN_ASPECT_RATIO = 16 / 9f;
276
Sascha Haeberling59c784b2014-08-05 10:53:08 -0700277 /** A directory to store debug information in during development. */
278 private final File mDebugDataDir;
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700279
280 /** CLEAN UP START */
281 // private SoundPool mSoundPool;
282 // private int mCaptureStartSoundId;
283 // private static final int NO_SOUND_STREAM = -999;
284 // private final int mCaptureStartSoundStreamId = NO_SOUND_STREAM;
285 // private int mCaptureDoneSoundId;
286 // private SoundClips.Player mSoundPlayer;
287 // private boolean mFirstLayout;
288 // private int[] mTargetFPSRanges;
289 // private float mZoomValue;
290 // private int mSensorOrientation;
291 // private int mLensFacing;
292 // private volatile float mMaxZoomRatio = 1.0f;
293 // private String mFlashMode;
294 /** CLEAN UP END */
295
296 /** Constructs a new capture module. */
297 public CaptureModule(AppController appController) {
298 super(appController);
299 mAppController = appController;
300 mContext = mAppController.getAndroidContext();
301 mSettingsManager = mAppController.getSettingsManager();
Sascha Haeberling0cf4a022014-07-31 11:36:58 -0700302 mSettingsManager.addListener(this);
Sascha Haeberling59c784b2014-08-05 10:53:08 -0700303 mDebugDataDir = mContext.getExternalCacheDir();
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700304 }
305
306 @Override
307 public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
308 Log.d(TAG, "init");
309 mIsResumeFromLockScreen = isResumeFromLockscreen(activity);
310 mMainHandler = new Handler(activity.getMainLooper());
311 mCameraManager = mAppController.getCameraManager();
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700312 mDisplayRotation = CameraUtil.getDisplayRotation(mContext);
313 mCameraFacing = getFacingFromCameraId(mSettingsManager.getInteger(
314 mAppController.getModuleScope(),
315 Keys.KEY_CAMERA_ID));
316 mUI = new CaptureModuleUI(activity, this, mAppController.getModuleLayoutRoot(),
317 mLayoutListener);
318 mAppController.setPreviewStatusListener(mUI);
319 mPreviewTexture = mAppController.getCameraAppUI().getSurfaceTexture();
320 if (mPreviewTexture != null) {
321 initSurface(mPreviewTexture);
322 }
323 }
324
325 @Override
326 public void onShutterButtonFocus(boolean pressed) {
327 // TODO Auto-generated method stub
328 }
329
330 @Override
331 public void onShutterCoordinate(TouchCoordinate coord) {
332 // TODO Auto-generated method stub
333 }
334
335 @Override
336 public void onShutterButtonClick() {
337 // TODO: Add focusing.
338 if (mCamera == null) {
339 return;
340 }
341
342 // Set up the capture session.
Sascha Haeberling57bcd922014-07-31 16:05:57 -0700343 long sessionTime = System.currentTimeMillis();
344 String title = CameraUtil.createJpegName(sessionTime);
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700345 CaptureSession session = getServices().getCaptureSessionManager()
Sascha Haeberling57bcd922014-07-31 16:05:57 -0700346 .createNewSession(title, sessionTime, null);
347
348 // TODO: Add location.
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700349
350 // Set up the parameters for this capture.
351 PhotoCaptureParameters params = new PhotoCaptureParameters();
Sascha Haeberling0cf4a022014-07-31 11:36:58 -0700352 params.title = title;
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700353 params.callback = this;
354 params.orientation = getOrientation();
Sascha Haeberling0cf4a022014-07-31 11:36:58 -0700355 params.flashMode = getFlashModeFromSettings();
Sascha Haeberling57bcd922014-07-31 16:05:57 -0700356 params.heading = mHeading;
Sascha Haeberling59c784b2014-08-05 10:53:08 -0700357 params.debugDataFolder = mDebugDataDir;
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700358
359 // Take the picture.
360 mCamera.takePicture(params, session);
361 }
362
363 @Override
364 public void onPreviewAreaChanged(RectF previewArea) {
Andy Huibers30ffce02014-07-31 16:31:43 -0700365 mPreviewArea = previewArea;
Sascha Haeberling0cf4a022014-07-31 11:36:58 -0700366 // mUI.updatePreviewAreaRect(previewArea);
367 // mUI.positionProgressOverlay(previewArea);
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700368 }
369
370 @Override
371 public void onSensorChanged(SensorEvent event) {
372 // This is literally the same as the GCamModule implementation.
373 int type = event.sensor.getType();
374 float[] data;
375 if (type == Sensor.TYPE_ACCELEROMETER) {
376 data = mGData;
377 } else if (type == Sensor.TYPE_MAGNETIC_FIELD) {
378 data = mMData;
379 } else {
380 Log.w(TAG, String.format("Unexpected sensor type %s", event.sensor.getName()));
381 return;
382 }
383 for (int i = 0; i < 3; i++) {
384 data[i] = event.values[i];
385 }
386 float[] orientation = new float[3];
387 SensorManager.getRotationMatrix(mR, null, mGData, mMData);
388 SensorManager.getOrientation(mR, orientation);
389 mHeading = (int) (orientation[0] * 180f / Math.PI) % 360;
390 if (mHeading < 0) {
391 mHeading += 360;
392 }
393 }
394
395 @Override
396 public void onAccuracyChanged(Sensor sensor, int accuracy) {
397 // TODO Auto-generated method stub
398 }
399
400 @Override
401 public void onQueueStatus(boolean full) {
402 // TODO Auto-generated method stub
403 }
404
405 @Override
406 public void onRemoteShutterPress() {
407 // TODO: Check whether shutter is enabled.
408 onShutterButtonClick();
409 }
410
411 @Override
412 public void onSurfaceTextureAvailable(final SurfaceTexture surface, int width, int height) {
413 Log.d(TAG, "onSurfaceTextureAvailable");
414 // Force to re-apply transform matrix here as a workaround for
415 // b/11168275
416 updatePreviewTransform(width, height, true);
417 initSurface(surface);
418 }
419
420 public void initSurface(final SurfaceTexture surface) {
421 mPreviewTexture = surface;
422 closeCamera();
423
424 mCameraManager.open(mCameraFacing, getPictureSizeFromSettings(), new OpenCallback() {
425 @Override
426 public void onFailure() {
427 Log.e(TAG, "Could not open camera.");
428 mCamera = null;
Sascha Haeberling0cf4a022014-07-31 11:36:58 -0700429 mAppController.showErrorAndFinish(R.string.cannot_connect_camera);
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700430 }
431
432 @Override
433 public void onCameraOpened(final OneCamera camera) {
434 Log.d(TAG, "onCameraOpened: " + camera);
435 mCamera = camera;
436 updateBufferDimension();
437
438 // If the surface texture is not destroyed, it may have the last
439 // frame lingering.
440 // We need to hold off setting transform until preview is
441 // started.
442 resetDefaultBufferSize();
443 mState = ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED;
444
445 Log.d(TAG, "starting preview ...");
446
447 // TODO: Consider rolling these two calls into one.
448 camera.startPreview(new Surface(surface), new CaptureReadyCallback() {
449
450 @Override
451 public void onSetupFailed() {
452 Log.e(TAG, "Could not set up preview.");
453 mCamera.close(null);
454 mCamera = null;
Sascha Haeberling0cf4a022014-07-31 11:36:58 -0700455 // TODO: Show an error message and exit.
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700456 }
457
458 @Override
459 public void onReadyForCapture() {
Sascha Haeberling0cf4a022014-07-31 11:36:58 -0700460 Log.d(TAG, "Ready for capture.");
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700461 onPreviewStarted();
Andy Huibers30ffce02014-07-31 16:31:43 -0700462 mCamera.setFocusStateListener(CaptureModule.this);
Sascha Haeberlinge3ad4352014-08-15 10:52:37 -0700463 mCamera.setReadyStateChangedListener(CaptureModule.this);
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700464 }
465 });
466 }
467 });
468 }
469
470 @Override
471 public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
472 Log.d(TAG, "onSurfaceTextureSizeChanged");
473 resetDefaultBufferSize();
474 }
475
476 @Override
477 public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
478 Log.d(TAG, "onSurfaceTextureDestroyed");
479 closeCamera();
480 return true;
481 }
482
483 @Override
484 public void onSurfaceTextureUpdated(SurfaceTexture surface) {
485 if (mState == ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE) {
486 Log.d(TAG, "onSurfaceTextureUpdated --> updatePreviewTransform");
487 mState = ModuleState.IDLE;
488 CameraAppUI appUI = mAppController.getCameraAppUI();
489 updatePreviewTransform(appUI.getSurfaceWidth(), appUI.getSurfaceHeight(), true);
490 }
491 }
492
493 @Override
494 public String getModuleStringIdentifier() {
495 return PHOTO_MODULE_STRING_ID;
496 }
497
498 @Override
499 public void resume() {
500 // Add delay on resume from lock screen only, in order to to speed up
501 // the onResume --> onPause --> onResume cycle from lock screen.
502 // Don't do always because letting go of thread can cause delay.
503 if (mIsResumeFromLockScreen) {
504 Log.v(TAG, "Delayng onResumeTasks from lock screen. " + System.currentTimeMillis());
505 // Note: onPauseAfterSuper() will delete this runnable, so we will
506 // at most have 1 copy queued up.
507 mMainHandler.postDelayed(mResumeTaskRunnable, ON_RESUME_TASKS_DELAY_MSEC);
508 } else {
509 onResumeTasks();
510 }
511 }
512
513 private void onResumeTasks() {
514 Log.d(TAG, "onResumeTasks + " + System.currentTimeMillis());
515 mPaused = false;
516 mAppController.getCameraAppUI().onChangeCamera();
517 mAppController.addPreviewAreaSizeChangedListener(this);
518 resetDefaultBufferSize();
519 getServices().getRemoteShutterListener().onModuleReady(this);
Sascha Haeberlinge3ad4352014-08-15 10:52:37 -0700520 // TODO: Check if we can really take a photo right now (memory, camera
521 // state, ... ).
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700522 mAppController.setShutterEnabled(true);
523 }
524
525 @Override
526 public void pause() {
527 mPaused = true;
528 resetTextureBufferSize();
529 closeCamera();
530 // Remove delayed resume trigger, if it hasn't been executed yet.
531 mMainHandler.removeCallbacksAndMessages(null);
532 }
533
534 @Override
535 public void destroy() {
536 }
537
538 @Override
539 public void onLayoutOrientationChanged(boolean isLandscape) {
540 Log.d(TAG, "onLayoutOrientationChanged");
541 }
542
543 @Override
544 public void onOrientationChanged(int orientation) {
545 // We keep the last known orientation. So if the user first orient
546 // the camera then point the camera to floor or sky, we still have
547 // the correct orientation.
548 if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
549 return;
550 }
551 mOrientation = CameraUtil.roundOrientation(orientation, mOrientation);
552 }
553
554 @Override
555 public void onCameraAvailable(CameraProxy cameraProxy) {
556 // Ignore since we manage the camera ourselves until we remove this.
557 }
558
559 @Override
560 public void hardResetSettings(SettingsManager settingsManager) {
561 // TODO Auto-generated method stub
562 }
563
564 @Override
565 public HardwareSpec getHardwareSpec() {
566 return new HardwareSpec() {
567 @Override
568 public boolean isFrontCameraSupported() {
569 return true;
570 }
571
572 @Override
573 public boolean isHdrSupported() {
574 return false;
575 }
576
577 @Override
578 public boolean isHdrPlusSupported() {
579 // TODO: Enable once we support this.
580 return false;
581 }
582
583 @Override
584 public boolean isFlashSupported() {
Sascha Haeberling0cf4a022014-07-31 11:36:58 -0700585 return true;
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700586 }
587 };
588 }
589
590 @Override
591 public BottomBarUISpec getBottomBarSpec() {
592 CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec();
593 bottomBarSpec.enableGridLines = true;
594 bottomBarSpec.enableCamera = true;
595 bottomBarSpec.cameraCallback = mCameraSwitchCallback;
596 // TODO: Enable once we support this.
597 bottomBarSpec.enableHdr = false;
598 // TODO: Enable once we support this.
599 bottomBarSpec.hdrCallback = null;
600 // TODO: Enable once we support this.
601 bottomBarSpec.enableSelfTimer = false;
602 bottomBarSpec.showSelfTimer = false;
Sascha Haeberling0cf4a022014-07-31 11:36:58 -0700603 // TODO: Deal with e.g. HDR+ if it doesn't support it.
604 bottomBarSpec.enableFlash = true;
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700605 return bottomBarSpec;
606 }
607
608 @Override
609 public boolean isUsingBottomBar() {
610 return true;
611 }
612
613 @Override
614 public boolean onKeyDown(int keyCode, KeyEvent event) {
615 return false;
616 }
617
618 @Override
619 public boolean onKeyUp(int keyCode, KeyEvent event) {
620 return false;
621 }
622
Andy Huibers30ffce02014-07-31 16:31:43 -0700623 /**
624 * Focus sequence starts for zone around tap location for single tap.
625 */
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700626 @Override
627 public void onSingleTapUp(View view, int x, int y) {
Andy Huibers30ffce02014-07-31 16:31:43 -0700628 Log.v(TAG, "onSingleTapUp x=" + x + " y=" + y);
629 // TODO: This should query actual capability.
630 if (mCameraFacing == Facing.FRONT) {
631 return;
632 }
633 triggerFocusAtScreenCoord(x, y);
634 }
635
636 // TODO: Consider refactoring FocusOverlayManager.
637 // Currently AF state transitions are controlled in OneCameraImpl.
638 // PhotoModule uses FocusOverlayManager which uses API1/portability
639 // logic and coordinates.
640
641 private void triggerFocusAtScreenCoord(int x, int y) {
642 mTapToFocusInProgress = true;
643 // Show UI immediately even though scan has not started yet.
644 mUI.setAutoFocusTarget(x, y);
645 mUI.showAutoFocusInProgress();
646 mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
647 mMainHandler.postDelayed(mHideAutoFocusTargetRunnable, FOCUS_HOLD_UI_TIMEOUT_MILLIS);
648
649 // Normalize coordinates to [0,1] per CameraOne API.
650 float points[] = new float[2];
651 points[0] = (x - mPreviewArea.left) / mPreviewArea.width();
652 points[1] = (y - mPreviewArea.top) / mPreviewArea.height();
653
654 // Rotate coordinates to portrait orientation per CameraOne API.
655 Matrix rotationMatrix = new Matrix();
656 rotationMatrix.setRotate(mDisplayRotation, 0.5f, 0.5f);
657 rotationMatrix.mapPoints(points);
658 mCamera.triggerFocusAndMeterAtPoint(points[0], points[1]);
659
660 // Log touch (screen coordinates).
661 if (mZoomValue == 1f) {
662 TouchCoordinate touchCoordinate = new TouchCoordinate(x - mPreviewArea.left,
663 y - mPreviewArea.top, mPreviewArea.width(), mPreviewArea.height());
664 // TODO: Add to logging: duration, rotation.
665 UsageStatistics.instance().tapToFocus(touchCoordinate, null);
666 }
667 }
668
669 /**
670 * This AF status listener does two things:
671 * <ol>
672 * <li>Ends tap-to-focus period when mode goes from AUTO to CONTINUOUS_PICTURE.</li>
673 * <li>Updates AF UI if tap-to-focus is not in progress.</li>
674 * </ol>
675 */
Sascha Haeberling59c784b2014-08-05 10:53:08 -0700676 @Override
Andy Huibers30ffce02014-07-31 16:31:43 -0700677 public void onFocusStatusUpdate(final AutoFocusMode mode, final AutoFocusState state) {
678 Log.v(TAG, "AF status is mode:" + mode + " state:" + state);
679
680 if (FOCUS_DEBUG_UI) {
681 // TODO: Add debug circle radius+color UI to FocusOverlay.
682 // mMainHandler.post(...)
683 }
684
685 // After tap to focus SCAN completes, clear UI after FOCUS_HOLD_UI_MILLIS.
686 if (mTapToFocusInProgress && mode == AutoFocusMode.AUTO &&
687 (state == AutoFocusState.STOPPED_FOCUSED ||
688 state == AutoFocusState.STOPPED_UNFOCUSED)) {
689 mMainHandler.postDelayed(new Runnable() {
690 @Override
691 public void run() {
692 mTapToFocusInProgress = false;
693 mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
694 mMainHandler.post(mHideAutoFocusTargetRunnable);
695 }
696 }, FOCUS_HOLD_UI_MILLIS);
697 }
698
699 // Use the OneCamera auto focus callbacks to show the UI, except for
700 // tap to focus where we show UI right away at touch, and then turn
701 // it off early at 0.5 sec, before the focus lock expires at 3 sec.
702 if (!mTapToFocusInProgress) {
703 switch (state) {
704 case SCANNING:
705 mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
706 mMainHandler.post(mShowAutoFocusTargetInCenterRunnable);
707 break;
708 case STOPPED_FOCUSED:
709 case STOPPED_UNFOCUSED:
710 mMainHandler.removeCallbacks(mHideAutoFocusTargetRunnable);
711 mMainHandler.post(mHideAutoFocusTargetRunnable);
712 break;
713 }
714 }
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700715 }
716
717 @Override
Sascha Haeberlinge3ad4352014-08-15 10:52:37 -0700718 public void onReadyStateChanged(boolean readyForCapture) {
719 mAppController.setShutterEnabled(readyForCapture);
720 }
721
722 @Override
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700723 public String getPeekAccessibilityString() {
724 return mAppController.getAndroidContext()
725 .getResources().getString(R.string.photo_accessibility_peek);
726 }
727
728 @Override
729 public void onThumbnailResult(Bitmap bitmap) {
730 // TODO
731 }
732
733 @Override
Sascha Haeberling57bcd922014-07-31 16:05:57 -0700734 public void onPictureTaken(CaptureSession session) {
Sascha Haeberling57bcd922014-07-31 16:05:57 -0700735 }
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700736
Sascha Haeberling57bcd922014-07-31 16:05:57 -0700737 @Override
738 public void onPictureSaved(Uri uri) {
739 mAppController.notifyNewMedia(uri);
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700740 }
741
742 @Override
743 public void onTakePictureProgress(int progressPercent) {
Sascha Haeberling0cf4a022014-07-31 11:36:58 -0700744 // TODO once we have HDR+ hooked up.
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700745 }
746
747 @Override
748 public void onPictureTakenFailed() {
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700749 }
750
Sascha Haeberling0cf4a022014-07-31 11:36:58 -0700751 @Override
752 public void onSettingChanged(SettingsManager settingsManager, String key) {
753 // TODO Auto-generated method stub
754 }
755
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700756 /**
757 * Updates the preview transform matrix to adapt to the current preview
758 * width, height, and orientation.
759 */
760 public void updatePreviewTransform() {
761 int width;
762 int height;
763 synchronized (mDimensionLock) {
764 width = mScreenWidth;
765 height = mScreenHeight;
766 }
767 updatePreviewTransform(width, height);
768 }
769
770 /**
771 * Called when the preview started. Informs the app controller and queues a
772 * transform update when the next preview frame arrives.
773 */
774 private void onPreviewStarted() {
775 if (mState == ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED) {
776 mState = ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE;
777 }
778 mAppController.onPreviewStarted();
779 }
780
781 /**
782 * Update the preview transform based on the new dimensions. Will not force
783 * an update, if it's not necessary.
784 */
785 private void updatePreviewTransform(int incomingWidth, int incomingHeight) {
786 updatePreviewTransform(incomingWidth, incomingHeight, false);
787 }
788
789 /***
790 * Update the preview transform based on the new dimensions.
Andy Huibers30ffce02014-07-31 16:31:43 -0700791 * TODO: Make work with all: aspect ratios/resolutions x screens/cameras.
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700792 */
793 private void updatePreviewTransform(int incomingWidth, int incomingHeight,
794 boolean forceUpdate) {
795 Log.d(TAG, "updatePreviewTransform: " + incomingWidth + " x " + incomingHeight);
796
797 synchronized (mDimensionLock) {
798 int incomingRotation = CameraUtil
799 .getDisplayRotation(mContext);
800 // Check for an actual change:
801 if (mScreenHeight == incomingHeight && mScreenWidth == incomingWidth &&
802 incomingRotation == mDisplayRotation && !forceUpdate) {
803 return;
804 }
805 // Update display rotation and dimensions
806 mDisplayRotation = incomingRotation;
807 mScreenWidth = incomingWidth;
808 mScreenHeight = incomingHeight;
809 updateBufferDimension();
810
Sascha Haeberling0cf4a022014-07-31 11:36:58 -0700811 mPreviewTranformationMatrix = mAppController.getCameraAppUI().getPreviewTransform(
812 mPreviewTranformationMatrix);
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700813 int width = mScreenWidth;
814 int height = mScreenHeight;
815
816 // Assumptions:
817 // - Aspect ratio for the sensor buffers is in landscape
818 // orientation,
819 // - Dimensions of buffers received are rotated to the natural
820 // device orientation.
821 // - The contents of each buffer are rotated by the inverse of
822 // the display rotation.
823 // - Surface scales the buffer to fit the current view bounds.
824
825 // Get natural orientation and buffer dimensions
826 int naturalOrientation = CaptureModuleUtil
827 .getDeviceNaturalOrientation(mContext);
828 int effectiveWidth = mPreviewBufferWidth;
829 int effectiveHeight = mPreviewBufferHeight;
830
831 if (DEBUG) {
832 Log.v(TAG, "Rotation: " + mDisplayRotation);
833 Log.v(TAG, "Screen Width: " + mScreenWidth);
834 Log.v(TAG, "Screen Height: " + mScreenHeight);
835 Log.v(TAG, "Buffer width: " + mPreviewBufferWidth);
836 Log.v(TAG, "Buffer height: " + mPreviewBufferHeight);
837 Log.v(TAG, "Natural orientation: " + naturalOrientation);
838 }
839
840 // If natural orientation is portrait, rotate the buffer
841 // dimensions
842 if (naturalOrientation == Configuration.ORIENTATION_PORTRAIT) {
843 int temp = effectiveWidth;
844 effectiveWidth = effectiveHeight;
845 effectiveHeight = temp;
846 }
847
848 // Find and center view rect and buffer rect
849 RectF viewRect = new RectF(0, 0, width, height);
850 RectF bufRect = new RectF(0, 0, effectiveWidth, effectiveHeight);
851 float centerX = viewRect.centerX();
852 float centerY = viewRect.centerY();
853 bufRect.offset(centerX - bufRect.centerX(), centerY - bufRect.centerY());
854
855 // Undo ScaleToFit.FILL done by the surface
856 mPreviewTranformationMatrix.setRectToRect(viewRect, bufRect, Matrix.ScaleToFit.FILL);
857
858 // Rotate buffer contents to proper orientation
Sascha Haeberling0cf4a022014-07-31 11:36:58 -0700859 mPreviewTranformationMatrix.postRotate(getPreviewOrientation(mDisplayRotation),
860 centerX, centerY);
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700861
862 // TODO: This is probably only working for the N5. Need to test
863 // on a device like N10 with different sensor orientation.
864 if ((mDisplayRotation % 180) == 90) {
865 int temp = effectiveWidth;
866 effectiveWidth = effectiveHeight;
867 effectiveHeight = temp;
868 }
869
870 boolean is16by9 = false;
871
872 // TODO: BACK/FRONT.
873 Size pictureSize = getPictureSizeFromSettings();
874 if (pictureSize != null) {
875 pictureSize = ResolutionUtil.getApproximateSize(pictureSize);
Sascha Haeberling0cf4a022014-07-31 11:36:58 -0700876 if (pictureSize.equals(new Size(16, 9))) {
877 is16by9 = true;
878 }
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700879 }
880
881 float scale;
882 if (is16by9) {
883 // We are going to be clipping off edges to achieve the 16
884 // by 9 aspect ratio so we will choose the max here to fill,
885 // instead of fit.
886 scale =
887 Math.max(width / (float) effectiveWidth, height
888 / (float) effectiveHeight);
889 } else {
890 // Scale to fit view, cropping the longest dimension
891 scale =
892 Math.min(width / (float) effectiveWidth, height
893 / (float) effectiveHeight);
894 }
895 mPreviewTranformationMatrix.postScale(scale, scale, centerX, centerY);
896
Andy Huibers30ffce02014-07-31 16:31:43 -0700897 // TODO: Take these quantities from mPreviewArea.
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700898 float previewWidth = effectiveWidth * scale;
899 float previewHeight = effectiveHeight * scale;
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700900 float previewCenterX = previewWidth / 2;
901 float previewCenterY = previewHeight / 2;
Sascha Haeberling0cf4a022014-07-31 11:36:58 -0700902 mPreviewTranformationMatrix.postTranslate(previewCenterX - centerX, previewCenterY
903 - centerY);
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700904
905 if (is16by9) {
906 float aspectRatio = FULLSCREEN_ASPECT_RATIO;
907 RectF renderedPreviewRect = mAppController.getFullscreenRect();
908 float desiredPreviewWidth = Math.max(renderedPreviewRect.height(),
909 renderedPreviewRect.width()) * 1 / aspectRatio;
910 int letterBoxWidth = (int) Math.ceil((Math.min(renderedPreviewRect.width(),
911 renderedPreviewRect.height()) - desiredPreviewWidth) / 2.0f);
912 mAppController.getCameraAppUI().addLetterboxing(letterBoxWidth);
913
914 float wOffset = -(previewWidth - renderedPreviewRect.width()) / 2.0f;
915 float hOffset = -(previewHeight - renderedPreviewRect.height()) / 2.0f;
916 mPreviewTranformationMatrix.postTranslate(wOffset, hOffset);
Sascha Haeberling0cf4a022014-07-31 11:36:58 -0700917 mAppController.updatePreviewTransformFullscreen(mPreviewTranformationMatrix,
918 aspectRatio);
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700919 } else {
920 mAppController.updatePreviewTransform(mPreviewTranformationMatrix);
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700921 mAppController.getCameraAppUI().hideLetterboxing();
922 }
923 // if (mGcamProxy != null) {
924 // mGcamProxy.postSetAspectRatio(mFinalAspectRatio);
925 // }
Sascha Haeberling0cf4a022014-07-31 11:36:58 -0700926 // mUI.updatePreviewAreaRect(new RectF(0, 0, previewWidth,
927 // previewHeight));
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700928
929 // TODO: Add face detection.
930 // Characteristics info =
931 // mapp.getCameraProvider().getCharacteristics(0);
932 // mUI.setupFaceDetection(CameraUtil.getDisplayOrientation(incomingRotation,
933 // info), false);
934 // updateCamera2FaceBoundTransform(new
935 // RectF(mEffectiveCropRegion),
936 // new RectF(0, 0, mBufferWidth, mBufferHeight),
937 // new RectF(0, 0, previewWidth, previewHeight), getRotation());
938 }
939 }
940
941 private void updateBufferDimension() {
942 if (mCamera == null) {
943 return;
944 }
945
946 Size picked = CaptureModuleUtil.pickBufferDimensions(
947 mCamera.getSupportedSizes(),
948 mCamera.getFullSizeAspectRatio(),
949 mContext);
950 mPreviewBufferWidth = picked.getWidth();
951 mPreviewBufferHeight = picked.getHeight();
952 }
953
954 /**
955 * Resets the default buffer size to the initially calculated size.
956 */
957 private void resetDefaultBufferSize() {
958 synchronized (mSurfaceLock) {
959 if (mPreviewTexture != null) {
960 mPreviewTexture.setDefaultBufferSize(mPreviewBufferWidth, mPreviewBufferHeight);
961 }
962 }
963 }
964
965 private void closeCamera() {
966 if (mCamera != null) {
Andy Huibers30ffce02014-07-31 16:31:43 -0700967 mCamera.setFocusStateListener(null);
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700968 mCamera.close(null);
969 mCamera = null;
970 }
971 }
972
973 private int getOrientation() {
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700974 if (mAppController.isAutoRotateScreen()) {
Sol Boucher73ec9852014-07-24 23:59:21 -0700975 return mDisplayRotation;
Sascha Haeberling08b3c942014-07-30 17:48:52 -0700976 } else {
977 return mOrientation;
978 }
979 }
980
981 /**
982 * @return Whether we are resuming from within the lockscreen.
983 */
984 private static boolean isResumeFromLockscreen(Activity activity) {
985 String action = activity.getIntent().getAction();
986 return (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(action)
987 || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action));
988 }
989
990 private void switchCamera(Facing switchTo) {
991 if (mPaused || mCameraFacing == switchTo) {
992 return;
993 }
994 // TODO: Un-comment once we have timer back.
995 // cancelCountDown();
996
997 mAppController.freezeScreenUntilPreviewReady();
998
999 mCameraFacing = switchTo;
1000 initSurface(mPreviewTexture);
1001
1002 // TODO: Un-comment once we have focus back.
1003 // if (mFocusManager != null) {
1004 // mFocusManager.removeMessages();
1005 // }
1006 // mFocusManager.setMirror(mMirror);
1007 }
1008
1009 private Size getPictureSizeFromSettings() {
1010 String pictureSizeKey = mCameraFacing == Facing.FRONT ? Keys.KEY_PICTURE_SIZE_FRONT
1011 : Keys.KEY_PICTURE_SIZE_BACK;
1012 return mSettingsManager.getSize(SettingsManager.SCOPE_GLOBAL, pictureSizeKey);
1013 }
1014
1015 private int getPreviewOrientation(int deviceOrientationDegrees) {
1016 // Important: Camera2 buffers are already rotated to the natural
1017 // orientation of the device (at least for the back-camera).
1018
1019 // TODO: Remove this hack for the front camera as soon as b/16637957 is
1020 // fixed.
1021 if (mCameraFacing == Facing.FRONT) {
1022 deviceOrientationDegrees += 180;
1023 }
1024 return (360 - deviceOrientationDegrees) % 360;
1025 }
1026
1027 /**
1028 * Returns which way around the camera is facing, based on it's ID.
1029 * <p>
1030 * TODO: This needs to change so that we store the direction directly in the
1031 * settings, rather than a Camera ID.
1032 */
1033 private static Facing getFacingFromCameraId(int cameraId) {
1034 return cameraId == 1 ? Facing.FRONT : Facing.BACK;
1035 }
1036
1037 private void resetTextureBufferSize() {
1038 // Reset the default buffer sizes on the shared SurfaceTexture
1039 // so they are not scaled for gcam.
1040 //
Sascha Haeberling0cf4a022014-07-31 11:36:58 -07001041 // According to the documentation for
1042 // SurfaceTexture.setDefaultBufferSize,
Sascha Haeberling08b3c942014-07-30 17:48:52 -07001043 // photo and video based image producers (presumably only Camera 1 api),
Sascha Haeberling0cf4a022014-07-31 11:36:58 -07001044 // override this buffer size. Any module that uses egl to render to a
1045 // SurfaceTexture must have these buffer sizes reset manually. Otherwise
1046 // the SurfaceTexture cannot be transformed by matrix set on the
1047 // TextureView.
Sascha Haeberling08b3c942014-07-30 17:48:52 -07001048 if (mPreviewTexture != null) {
1049 mPreviewTexture.setDefaultBufferSize(mAppController.getCameraAppUI().getSurfaceWidth(),
Sascha Haeberling0cf4a022014-07-31 11:36:58 -07001050 mAppController.getCameraAppUI().getSurfaceHeight());
1051 }
1052 }
1053
1054 /**
1055 * @return The currently set Flash settings. Defaults to AUTO if the setting
1056 * could not be parsed.
1057 */
1058 private Flash getFlashModeFromSettings() {
1059 String flashSetting = mSettingsManager.getString(mAppController.getCameraScope(),
1060 Keys.KEY_FLASH_MODE);
1061 try {
1062 return Flash.valueOf(flashSetting.toUpperCase());
1063 } catch (IllegalArgumentException ex) {
1064 Log.w(TAG, "Could not parse Flash Setting. Defaulting to AUTO.");
1065 return Flash.AUTO;
Sascha Haeberling08b3c942014-07-30 17:48:52 -07001066 }
1067 }
1068}