/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.camera; import android.content.Context; import android.content.Intent; import android.graphics.Matrix; import android.graphics.Point; import android.graphics.RectF; import android.graphics.SurfaceTexture; import android.location.Location; import android.hardware.camera2.CameraAccessException; import android.hardware.camera2.CameraCharacteristics; import android.hardware.camera2.CameraManager; import android.media.MediaActionSound; import android.net.Uri; import android.os.AsyncTask; import android.os.Handler; import android.os.HandlerThread; import android.os.SystemClock; import android.os.SystemProperties; import android.os.Bundle; import android.view.GestureDetector; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; import android.view.View; import android.provider.MediaStore; import com.android.camera.app.AppController; import com.android.camera.app.CameraAppUI; import com.android.camera.app.CameraAppUI.BottomBarUISpec; import com.android.camera.app.LocationManager; import com.android.camera.app.OrientationManager.DeviceOrientation; import com.android.camera.async.MainThread; import com.android.camera.burst.BurstFacade; import com.android.camera.burst.BurstFacadeFactory; import com.android.camera.burst.BurstReadyStateChangeListener; import com.android.camera.burst.OrientationLockController; import com.android.camera.captureintent.PreviewTransformCalculator; import com.android.camera.debug.DebugPropertyHelper; import com.android.camera.debug.Log; import com.android.camera.debug.Log.Tag; import com.android.camera.device.CameraId; import com.android.camera.hardware.HardwareSpec; import com.android.camera.hardware.HeadingSensor; import com.android.camera.module.ModuleController; import com.android.camera.one.OneCamera; import com.android.camera.one.OneCamera.AutoFocusState; import com.android.camera.one.OneCamera.CaptureReadyCallback; import com.android.camera.one.OneCamera.Facing; import com.android.camera.one.OneCamera.OpenCallback; import com.android.camera.one.OneCamera.PhotoCaptureParameters; import com.android.camera.one.OneCameraAccessException; import com.android.camera.one.OneCameraCaptureSetting; import com.android.camera.one.OneCameraCharacteristics; import com.android.camera.one.OneCameraException; import com.android.camera.one.OneCameraManager; import com.android.camera.one.OneCameraModule; import com.android.camera.one.OneCameraOpener; import com.android.camera.one.config.OneCameraFeatureConfig; import com.android.camera.one.v2.photo.ImageRotationCalculator; import com.android.camera.one.v2.photo.ImageRotationCalculatorImpl; import com.android.camera.remote.RemoteCameraModule; import com.android.camera.session.CaptureSession; import com.android.camera.settings.Keys; import com.android.camera.settings.SettingsManager; import com.android.camera.stats.CaptureStats; import com.android.camera.stats.UsageStatistics; import com.android.camera.stats.profiler.Profile; import com.android.camera.stats.profiler.Profiler; import com.android.camera.stats.profiler.Profilers; import com.android.camera.ui.CountDownView; import com.android.camera.ui.PreviewStatusListener; import com.android.camera.ui.TouchCoordinate; import com.android.camera.ui.focus.FocusController; import com.android.camera.ui.focus.FocusSound; import com.android.camera.util.AndroidServices; import com.android.camera.util.ApiHelper; import com.android.camera.util.CameraUtil; import com.android.camera.util.GcamHelper; import com.android.camera.util.Size; import com.android.camera2.R; import com.android.ex.camera2.portability.CameraAgent.CameraProxy; import com.android.ex.camera2.portability.CameraDeviceInfo.Characteristics; import com.google.common.logging.eventprotos; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.Set; import javax.annotation.Nonnull; /** * New Capture module that is made to support photo and video capture on top of * the OneCamera API, to transparently support GCam. *
* This has been a re-write with pieces taken and improved from GCamModule and * PhotoModule, which are to be retired eventually. *
*/
public class CaptureModule extends CameraModule implements
ModuleController,
CountDownView.OnCountDownStatusListener,
OneCamera.PictureCallback,
OneCamera.FocusStateListener,
OneCamera.ReadyStateChangedListener,
RemoteCameraModule {
private static final Tag TAG = new Tag("CaptureModule");
/** Enable additional debug output. */
private static final boolean DEBUG = true;
/** Workaround Flag for b/19271661 to use autotransformation in Capture Layout in Nexus4 **/
private static final boolean USE_AUTOTRANSFORM_UI_LAYOUT = ApiHelper.IS_NEXUS_4;
/** Timeout for camera open/close operations. */
private static final int CAMERA_OPEN_CLOSE_TIMEOUT_MILLIS = 2500;
/** System Properties switch to enable debugging focus UI. */
private static final boolean CAPTURE_DEBUG_UI = DebugPropertyHelper.showCaptureDebugUI();
private final Object mDimensionLock = new Object();
/**
* Sticky Gcam mode is when this module's sole purpose it to be the Gcam
* mode. If true, the device uses {@link PhotoModule} for normal picture
* taking.
*/
private final boolean mStickyGcamCamera;
/** Controller giving us access to other services. */
private final AppController mAppController;
/** The applications settings manager. */
private final SettingsManager mSettingsManager;
/** Application context. */
private final Context mContext;
/** Module UI. */
private CaptureModuleUI mUI;
/** The camera manager used to open cameras. */
private OneCameraOpener mOneCameraOpener;
/** The manager to query for camera device information */
private OneCameraManager mOneCameraManager;
/** The currently opened camera device, or null if the camera is closed. */
private OneCamera mCamera;
private CameraActivity mActivity;
/** The selected picture size. */
private Size mPictureSize;
/** Fair semaphore held when opening or closing the camera. */
private final Semaphore mCameraOpenCloseLock = new Semaphore(1, true);
/** The direction the currently opened camera is facing to. */
private Facing mCameraFacing;
/** Whether HDR Scene mode is currently enabled. */
private boolean mHdrSceneEnabled = false;
private boolean mHdrPlusEnabled = false;
private final Object mSurfaceTextureLock = new Object();
/**
* Flag that is used when Fatal Error Handler is running and the app should
* not continue execution
*/
private boolean mShowErrorAndFinish;
private TouchCoordinate mLastShutterTouchCoordinate = null;
private FocusController mFocusController;
private OneCameraCharacteristics mCameraCharacteristics;
final private PreviewTransformCalculator mPreviewTransformCalculator;
/** The listener to listen events from the CaptureModuleUI. */
private final CaptureModuleUI.CaptureModuleUIListener mUIListener =
new CaptureModuleUI.CaptureModuleUIListener() {
@Override
public void onZoomRatioChanged(float zoomRatio) {
mZoomValue = zoomRatio;
if (mCamera != null) {
mCamera.setZoom(zoomRatio);
}
}
};
/** The listener to respond preview area changes. */
private final PreviewStatusListener.PreviewAreaChangedListener mPreviewAreaChangedListener =
new PreviewStatusListener.PreviewAreaChangedListener() {
@Override
public void onPreviewAreaChanged(RectF previewArea) {
mPreviewArea = previewArea;
mFocusController.configurePreviewDimensions(previewArea);
}
};
/** The listener to listen events from the preview. */
private final PreviewStatusListener mPreviewStatusListener = new PreviewStatusListener() {
@Override
public void onPreviewLayoutChanged(View v, int left, int top, int right,
int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
int width = right - left;
int height = bottom - top;
updatePreviewTransform(width, height, false);
}
@Override
public boolean shouldAutoAdjustTransformMatrixOnLayout() {
return USE_AUTOTRANSFORM_UI_LAYOUT;
}
@Override
public void onPreviewFlipped() {
// Do nothing because when preview is flipped, TextureView will lay
// itself out again, which will then trigger a transform matrix
// update.
}
@Override
public GestureDetector.OnGestureListener getGestureListener() {
return new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onSingleTapUp(MotionEvent ev) {
Point tapPoint = new Point((int) ev.getX(), (int) ev.getY());
Log.v(TAG, "onSingleTapUpPreview location=" + tapPoint);
if (!mCameraCharacteristics.isAutoExposureSupported() &&
!mCameraCharacteristics.isAutoFocusSupported()) {
return false;
}
startActiveFocusAt(tapPoint.x, tapPoint.y);
return true;
}
};
}
@Override
public View.OnTouchListener getTouchListener() {
return null;
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
Log.d(TAG, "onSurfaceTextureAvailable");
// Force to re-apply transform matrix here as a workaround for
// b/11168275
updatePreviewTransform(width, height, true);
synchronized (mSurfaceTextureLock) {
mPreviewSurfaceTexture = surface;
}
reopenCamera();
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
Log.d(TAG, "onSurfaceTextureDestroyed");
synchronized (mSurfaceTextureLock) {
mPreviewSurfaceTexture = null;
}
closeCamera();
return true;
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
Log.d(TAG, "onSurfaceTextureSizeChanged");
updatePreviewBufferSize();
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
if (mState == ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE) {
Log.d(TAG, "onSurfaceTextureUpdated --> updatePreviewTransform");
mState = ModuleState.IDLE;
CameraAppUI appUI = mAppController.getCameraAppUI();
updatePreviewTransform(appUI.getSurfaceWidth(), appUI.getSurfaceHeight(), true);
}
}
};
private final OneCamera.PictureSaverCallback mPictureSaverCallback =
new OneCamera.PictureSaverCallback() {
@Override
public void onRemoteThumbnailAvailable(final byte[] jpegImage) {
mMainThread.execute(new Runnable() {
@Override
public void run() {
mAppController.getServices().getRemoteShutterListener()
.onPictureTaken(jpegImage);
}
});
}
};
/** State by the module state machine. */
private static enum ModuleState {
IDLE,
WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED,
UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE,
}
/** The current state of the module. */
private ModuleState mState = ModuleState.IDLE;
/** Current zoom value. */
private float mZoomValue = 1f;
/** Records beginning frame of each AF scan. */
private long mAutoFocusScanStartFrame = -1;
/** Records beginning time of each AF scan in uptimeMillis. */
private long mAutoFocusScanStartTime;
/** Heading sensor. */
private HeadingSensor mHeadingSensor;
/** Used to fetch and embed the location into captured images. */
private final LocationManager mLocationManager;
/** Plays sounds for countdown timer. */
private SoundPlayer mSoundPlayer;
private final MediaActionSound mMediaActionSound;
/** Whether the module is paused right now. */
private boolean mPaused;
/** Main thread. */
private final MainThread mMainThread;
/** Handler thread for camera-related operations. */
private Handler mCameraHandler;
/** Current display rotation in degrees. */
private int mDisplayRotation;
/** Current screen width in pixels. */
private int mScreenWidth;
/** Current screen height in pixels. */
private int mScreenHeight;
/** Current width of preview frames from camera. */
private int mPreviewBufferWidth;
/** Current height of preview frames from camera.. */
private int mPreviewBufferHeight;
/** Area used by preview. */
RectF mPreviewArea;
/** The surface texture for the preview. */
private SurfaceTexture mPreviewSurfaceTexture;
/** The burst manager for controlling the burst. */
private final BurstFacade mBurstController;
private static final String BURST_SESSIONS_DIR = "burst_sessions";
private final Profiler mProfiler = Profilers.instance().guard();
public CaptureModule(AppController appController) {
this(appController, false);
}
private CameraManager mCameraManager;
/** Constructs a new capture module. */
public CaptureModule(AppController appController, boolean stickyHdr) {
super(appController);
Profile guard = mProfiler.create("new CaptureModule").start();
mPaused = true;
mMainThread = MainThread.create();
mAppController = appController;
mContext = mAppController.getAndroidContext();
mSettingsManager = mAppController.getSettingsManager();
mStickyGcamCamera = stickyHdr;
mLocationManager = mAppController.getLocationManager();
mPreviewTransformCalculator = new PreviewTransformCalculator(
mAppController.getOrientationManager());
mBurstController = BurstFacadeFactory.create(mContext,
new OrientationLockController() {
@Override
public void unlockOrientation() {
mAppController.getOrientationManager().unlockOrientation();
}
@Override
public void lockOrientation() {
mAppController.getOrientationManager().lockOrientation();
}
},
new BurstReadyStateChangeListener() {
@Override
public void onBurstReadyStateChanged(boolean ready) {
// TODO: This needs to take into account the state of
// the whole system, not just burst.
onReadyStateChanged(false);
}
});
mMediaActionSound = new MediaActionSound();
guard.stop();
}
private boolean updateCameraCharacteristics() {
try {
CameraId cameraId = mOneCameraManager.findFirstCameraFacing(mCameraFacing);
if (cameraId != null && cameraId.getValue() != null) {
mCameraCharacteristics = mOneCameraManager.getOneCameraCharacteristics(cameraId);
return mCameraCharacteristics != null;
}
} catch (OneCameraAccessException ignored) { }
//mAppController.getFatalErrorHandler().onGenericCameraAccessFailure();
return false;
}
@Override
public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
Profile guard = mProfiler.create("CaptureModule.init").start();
Log.d(TAG, "init UseAutotransformUiLayout = " + USE_AUTOTRANSFORM_UI_LAYOUT);
HandlerThread thread = new HandlerThread("CaptureModule.mCameraHandler");
thread.start();
mCameraHandler = new Handler(thread.getLooper());
mActivity = activity;
mOneCameraOpener = mAppController.getCameraOpener();
mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
try {
mOneCameraManager = OneCameraModule.provideOneCameraManager();
} catch (OneCameraException e) {
Log.e(TAG, "Unable to provide a OneCameraManager. ", e);
}
mDisplayRotation = CameraUtil.getDisplayRotation();
if ("homlet".equals(SystemProperties.get("ro.product.platform"))) {
mSettingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID,0);
}
mCameraFacing = getFacingFromCameraId(
mSettingsManager.getInteger(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID));
Log.d(TAG,"SettingsManager cameraID = " +
mSettingsManager.getInteger(mAppController.getModuleScope(),
Keys.KEY_CAMERA_ID) + " facing = " + mCameraFacing);
int cameraId = mSettingsManager.getInteger(mAppController.getModuleScope(),
Keys.KEY_CAMERA_ID);
try {
String[] cameraIds = mCameraManager.getCameraIdList();
Log.d(TAG, "cameraIds len = " + cameraIds.length);
boolean foundCameraId = false;
for (String currentCameraId : cameraIds) {
Log.d(TAG,"Enumerate cameraId = " + currentCameraId);
if (Integer.parseInt(currentCameraId) == cameraId) {
Log.d(TAG, "find exists currentCameraId matches first camera id");
foundCameraId = true;
break;
}
}
if (!foundCameraId && cameraIds.length > 0) {
cameraId = Integer.parseInt(cameraIds[0]);
Log.d(TAG,"cameraId sets to first id of cameraIds: " + cameraId);
}
} catch (CameraAccessException ex) {
Log.w(TAG, "Unable to get camera ID", ex);
}
mShowErrorAndFinish = !updateCameraCharacteristics();
if (mShowErrorAndFinish) {
return;
}
mUI = new CaptureModuleUI(activity, mAppController.getModuleLayoutRoot(), mUIListener);
mAppController.setPreviewStatusListener(mPreviewStatusListener);
synchronized (mSurfaceTextureLock) {
mPreviewSurfaceTexture = mAppController.getCameraAppUI().getSurfaceTexture();
}
mSoundPlayer = new SoundPlayer(mContext);
FocusSound focusSound = new FocusSound(mSoundPlayer, R.raw.material_camera_focus);
mFocusController = new FocusController(mUI.getFocusRing(), focusSound, mMainThread);
mHeadingSensor = new HeadingSensor(AndroidServices.instance().provideSensorManager());
View cancelButton = activity.findViewById(R.id.shutter_cancel_button);
cancelButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
cancelCountDown();
}
});
mMediaActionSound.load(MediaActionSound.SHUTTER_CLICK);
guard.stop();
}
private void handleVoiceTackPictrueIntent(){
if(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(mActivity.getIntent().getAction())){
if(mActivity.isVoiceInteractionRoot()){
if(mActivity.getIntent()!=null){
Bundle extras = mActivity.getIntent().getExtras();
boolean extra = false;
if(extras.containsKey("com.google.assistant.extra.CAMERA_OPEN_ONLY")){
extra = extras.getBoolean("com.google.assistant.extra.CAMERA_OPEN_ONLY",false);
}
if( extra){
return;
}
final Set
* TODO: Remove this once we use CaptureModule for photo taking.
*/
private void switchToRegularCapture() {
// Turn off HDR+ before switching back to normal photo mode.
SettingsManager settingsManager = mAppController.getSettingsManager();
settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, false);
// Disable this button to prevent callbacks from this module from firing
// while we are transitioning modules.
ButtonManager buttonManager = mAppController.getButtonManager();
buttonManager.disableButtonClick(ButtonManager.BUTTON_HDR_PLUS);
mAppController.getCameraAppUI().freezeScreenUntilPreviewReady();
mAppController.onModeSelected(mContext.getResources().getInteger(
R.integer.camera_mode_photo));
buttonManager.enableButtonClick(ButtonManager.BUTTON_HDR_PLUS);
}
/**
* Called when the preview started. Informs the app controller and queues a
* transform update when the next preview frame arrives.
*/
private void onPreviewStarted() {
if (mState == ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED) {
mState = ModuleState.UPDATE_TRANSFORM_ON_NEXT_SURFACE_TEXTURE_UPDATE;
}
mAppController.onPreviewStarted();
handleVoiceTackPictrueIntent();
}
/**
* Update the preview transform based on the new dimensions. Will not force
* an update, if it's not necessary.
*/
private void updatePreviewTransform(int incomingWidth, int incomingHeight) {
updatePreviewTransform(incomingWidth, incomingHeight, false);
}
/**
* Returns whether it is necessary to apply device-specific fix for b/19271661
* on the AutoTransform Path, i.e. USE_AUTOTRANSFORM_UI_LAYOUT == true
*
* @return whether to apply workaround fix for b/19271661
*/
private boolean requiresNexus4SpecificFixFor16By9Previews() {
return USE_AUTOTRANSFORM_UI_LAYOUT && ApiHelper.IS_NEXUS_4
&& is16by9AspectRatio(mPictureSize);
}
/***
* Update the preview transform based on the new dimensions. TODO: Make work
* with all: aspect ratios/resolutions x screens/cameras.
*/
private void updatePreviewTransform(int incomingWidth, int incomingHeight,
boolean forceUpdate) {
Log.d(TAG, "updatePreviewTransform: " + incomingWidth + " x " + incomingHeight);
synchronized (mDimensionLock) {
int incomingRotation = CameraUtil.getDisplayRotation();
// Check for an actual change:
if (mScreenHeight == incomingHeight && mScreenWidth == incomingWidth &&
incomingRotation == mDisplayRotation && !forceUpdate) {
return;
}
// Update display rotation and dimensions
mDisplayRotation = incomingRotation;
mScreenWidth = incomingWidth;
mScreenHeight = incomingHeight;
updatePreviewBufferDimension();
// Assumptions:
// - Aspect ratio for the sensor buffers is in landscape
// orientation,
// - Dimensions of buffers received are rotated to the natural
// device orientation.
// - The contents of each buffer are rotated by the inverse of
// the display rotation.
// - Surface scales the buffer to fit the current view bounds.
// Get natural orientation and buffer dimensions
if(USE_AUTOTRANSFORM_UI_LAYOUT) {
// Use PhotoUI-based AutoTransformation Interface
if (mPreviewBufferWidth != 0 && mPreviewBufferHeight != 0) {
if (requiresNexus4SpecificFixFor16By9Previews()) {
// Force preview size to be 16:9, even though surface is 4:3
// Surface content is assumed to be 16:9.
mAppController.updatePreviewAspectRatio(16.f / 9.f);
} else {
mAppController.updatePreviewAspectRatio(
mPreviewBufferWidth / (float) mPreviewBufferHeight);
}
}
} else {
Matrix transformMatrix = mPreviewTransformCalculator.toTransformMatrix(
new Size(mScreenWidth, mScreenHeight),
new Size(mPreviewBufferWidth, mPreviewBufferHeight));
mAppController.updatePreviewTransform(transformMatrix);
}
}
}
/**
* Calculates whether a picture size is 16:9 ratio, regardless of its
* orientation.
*
* @param size the size of the picture to be considered
* @return true, if the picture is 16:9; false if it's invalid or size is null
*/
private boolean is16by9AspectRatio(Size size) {
if (size == null || size.getWidth() == 0 || size.getHeight() == 0) {
return false;
}
// Normalize aspect ratio to be greater than 1.
final float aspectRatio = (size.getHeight() > size.getWidth())
? (size.getHeight() / (float) size.getWidth())
: (size.getWidth() / (float) size.getHeight());
return Math.abs(aspectRatio - (16.f / 9.f)) < 0.001f;
}
/**
* Based on the current picture size, selects the best preview dimension and
* stores it in {@link #mPreviewBufferWidth} and
* {@link #mPreviewBufferHeight}.
*/
private void updatePreviewBufferDimension() {
if (mCamera == null) {
return;
}
Size previewBufferSize = mCamera.pickPreviewSize(mPictureSize, mContext);
mPreviewBufferWidth = previewBufferSize.getWidth();
mPreviewBufferHeight = previewBufferSize.getHeight();
// Workaround for N4 TextureView/HAL issues b/19271661 for 16:9 preview
// streams.
if (requiresNexus4SpecificFixFor16By9Previews()) {
// Override the preview selection logic to the largest N4 4:3
// preview size but pass in 16:9 aspect ratio in
// UpdatePreviewAspectRatio later.
mPreviewBufferWidth = 1280;
mPreviewBufferHeight = 960;
}
updatePreviewBufferSize();
}
/**
* Open camera and start the preview.
*/
private void openCameraAndStartPreview() {
Profile guard = mProfiler.create("CaptureModule.openCameraAndStartPreview()").start();
try {
// TODO Given the current design, we cannot guarantee that one of
// CaptureReadyCallback.onSetupFailed or onReadyForCapture will
// be called (see below), so it's possible that
// mCameraOpenCloseLock.release() is never called under extremely
// rare cases. If we leak the lock, this timeout ensures that we at
// least crash so we don't deadlock the app.
if (!mCameraOpenCloseLock.tryAcquire(CAMERA_OPEN_CLOSE_TIMEOUT_MILLIS,
TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Time out waiting to acquire camera-open lock.");
}
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while waiting to acquire camera-open lock.", e);
}
guard.mark("Acquired mCameraOpenCloseLock");
if (mOneCameraOpener == null) {
Log.e(TAG, "no available OneCameraManager, showing error dialog");
mCameraOpenCloseLock.release();
mAppController.getFatalErrorHandler().onGenericCameraAccessFailure();
guard.stop("No OneCameraManager");
return;
}
if (mCamera != null) {
// If the camera is already open, do nothing.
Log.d(TAG, "Camera already open, not re-opening.");
mCameraOpenCloseLock.release();
guard.stop("Camera is already open");
return;
}
// Derive objects necessary for camera creation.
MainThread mainThread = MainThread.create();
ImageRotationCalculator imageRotationCalculator = ImageRotationCalculatorImpl
.from(mAppController.getOrientationManager(), mCameraCharacteristics);
// Only enable GCam on the back camera
boolean useHdr = mHdrPlusEnabled && mCameraFacing == Facing.BACK;
CameraId cameraId = mOneCameraManager.findFirstCameraFacing(mCameraFacing);
int settingsCameraId = mSettingsManager.getInteger(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID);
//check if settingsCameraId exists now
try {
String[] cameraIds = mCameraManager.getCameraIdList();
boolean foundCameraId = false;
for (String currentCameraId : cameraIds) {
Log.d(TAG,"Enumerate cameraId = " + currentCameraId);
if (Integer.parseInt(currentCameraId) == settingsCameraId) {
Log.d(TAG, "find exists currentCameraId matches first camera id");
foundCameraId = true;
if (Integer.parseInt(cameraId.getValue()) != settingsCameraId) {
cameraId = CameraId.from(String.valueOf(settingsCameraId));
}
break;
}
}
if (!foundCameraId && cameraIds.length > 0) {
cameraId = CameraId.from(String.valueOf(cameraIds[0]));
Log.d(TAG,"cameraId sets to first id of cameraIds: " + cameraId);
}
} catch (CameraAccessException ex) {
Log.w(TAG, "Unable to get camera ID", ex);
}
final String settingScope = SettingsManager.getCameraSettingScope(cameraId.getValue());
OneCameraCaptureSetting captureSetting;
// Read the preferred picture size from the setting.
try {
mPictureSize = mAppController.getResolutionSetting().getPictureSize(
cameraId, mCameraFacing);
captureSetting = OneCameraCaptureSetting.create(mPictureSize, mSettingsManager,
getHardwareSpec(), settingScope, useHdr);
} catch (OneCameraAccessException ex) {
mAppController.getFatalErrorHandler().onGenericCameraAccessFailure();
return;
}
mSettingsManager.set(mAppController.getModuleScope(),
Keys.KEY_CAMERA_ID, cameraId.getValue());
Log.d(TAG,"CameraOpener open cameraId = " + cameraId);
mOneCameraOpener.open(cameraId, captureSetting, mCameraHandler, mainThread,
imageRotationCalculator, mBurstController, mSoundPlayer,
new OpenCallback() {
@Override
public void onFailure() {
Log.e(TAG, "Could not open camera.");
// Sometimes the failure happens due to the controller
// being in paused state but mCamera is already
// initialized. In these cases we just need to close the
// camera device without showing the error dialog.
// Application will properly reopen the camera on the next
// resume operation (b/21025113).
boolean isControllerPaused = mAppController.isPaused();
if (mCamera != null) {
mCamera.close();
}
mCamera = null;
mCameraOpenCloseLock.release();
if (!isControllerPaused) {
mAppController.getFatalErrorHandler().onCameraOpenFailure();
}
}
@Override
public void onCameraClosed() {
mCamera = null;
mCameraOpenCloseLock.release();
}
@Override
public void onCameraOpened(@Nonnull final OneCamera camera) {
Log.d(TAG, "onCameraOpened: " + camera);
mCamera = camera;
// A race condition exists where the camera may be in the process
// of opening (blocked), but the activity gets destroyed. If the
// preview is initialized or callbacks are invoked on a destroyed
// activity, bad things can happen.
if (mAppController.isPaused()) {
onFailure();
return;
}
// When camera is opened, the zoom is implicitly reset to 1.0f
mZoomValue = 1.0f;
updatePreviewBufferDimension();
// If the surface texture is not destroyed, it may have
// the last frame lingering. We need to hold off setting
// transform until preview is started.
updatePreviewBufferSize();
mState = ModuleState.WATCH_FOR_NEXT_FRAME_AFTER_PREVIEW_STARTED;
Log.d(TAG, "starting preview ...");
// TODO: make mFocusController final and remove null
// check.
if (mFocusController != null) {
camera.setFocusDistanceListener(mFocusController);
}
mMainThread.execute(new Runnable() {
@Override
public void run() {
mAppController.getCameraAppUI().onChangeCamera();
mAppController.getButtonManager().enableCameraButton();
}
});
// TODO: Consider rolling these two calls into one.
camera.startPreview(new Surface(getPreviewSurfaceTexture()),
new CaptureReadyCallback() {
@Override
public void onSetupFailed() {
// We must release this lock here,
// before posting to the main handler
// since we may be blocked in pause(),
// getting ready to close the camera.
mCameraOpenCloseLock.release();
Log.e(TAG, "Could not set up preview.");
mMainThread.execute(new Runnable() {
@Override
public void run() {
if (mCamera == null) {
Log.d(TAG, "Camera closed, aborting.");
return;
}
mCamera.close();
mCamera = null;
// TODO: Show an error message
// and exit.
}
});
}
@Override
public void onReadyForCapture() {
// We must release this lock here,
// before posting to the main handler
// since we may be blocked in pause(),
// getting ready to close the camera.
mCameraOpenCloseLock.release();
mMainThread.execute(new Runnable() {
@Override
public void run() {
Log.d(TAG, "Ready for capture.");
if (mCamera == null) {
Log.d(TAG, "Camera closed, aborting.");
return;
}
onPreviewStarted();
// May be overridden by
// subsequent call to
// onReadyStateChanged().
onReadyStateChanged(true);
mCamera.setReadyStateChangedListener(
CaptureModule.this);
// Enable zooming after preview
// has started.
mUI.initializeZoom(mCamera.getMaxZoom());
mCamera.setFocusStateListener(CaptureModule.this);
}
});
}
});
}
}, mAppController.getFatalErrorHandler());
guard.stop("mOneCameraOpener.open()");
}
private void closeCamera() {
Profile profile = mProfiler.create("CaptureModule.closeCamera()").start();
try {
mCameraOpenCloseLock.acquire();
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while waiting to acquire camera-open lock.", e);
}
profile.mark("mCameraOpenCloseLock.acquire()");
try {
if (mCamera != null) {
mCamera.close();
profile.mark("mCamera.close()");
mCamera.setFocusStateListener(null);
mCamera = null;
}
} finally {
mCameraOpenCloseLock.release();
}
profile.stop();
}
/**
* Re-initialize the camera if e.g. the HDR mode or facing property changed.
*/
private void switchCamera() {
if (mShowErrorAndFinish) {
return;
}
if (mPaused) {
return;
}
cancelCountDown();
mAppController.freezeScreenUntilPreviewReady();
initSurfaceTextureConsumer();
}
/**
* Returns which way around the camera is facing, based on it's ID.
*
* TODO: This needs to change so that we store the direction directly in the
* settings, rather than a Camera ID.
*/
private Facing getFacingFromCameraId(int cameraId) {
Characteristics mCharacteristics = mAppController.getCameraProvider().getCharacteristics(cameraId);
if(mCharacteristics == null){
Log.e(TAG, "Can't find camera characteristics");
return Facing.NONE;
}
else if(mCharacteristics.isFacingFront()){
return Facing.FRONT;
}
else if(mCharacteristics.isFacingBack()){
return Facing.BACK;
}
else {
return Facing.EXTERNAL;
}
}
private void resetTextureBufferSize() {
// According to the documentation for
// SurfaceTexture.setDefaultBufferSize,
// photo and video based image producers (presumably only Camera 1 api),
// override this buffer size. Any module that uses egl to render to a
// SurfaceTexture must have these buffer sizes reset manually. Otherwise
// the SurfaceTexture cannot be transformed by matrix set on the
// TextureView.
updatePreviewBufferSize();
}
}