/*
|
* Copyright (C) 2012 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.annotation.TargetApi;
|
import android.app.Activity;
|
import android.content.ContentResolver;
|
import android.content.Intent;
|
import android.graphics.Bitmap;
|
import android.graphics.BitmapFactory;
|
import android.graphics.SurfaceTexture;
|
import android.location.Location;
|
import android.media.CameraProfile;
|
import android.net.Uri;
|
import android.os.AsyncTask;
|
import android.os.Build;
|
import android.os.Bundle;
|
import android.os.Handler;
|
import android.os.Looper;
|
import android.os.Message;
|
import android.os.MessageQueue;
|
import android.os.SystemClock;
|
import android.provider.MediaStore;
|
import android.view.KeyEvent;
|
import android.view.View;
|
|
import com.android.camera.PhotoModule.NamedImages.NamedEntity;
|
import com.android.camera.app.AppController;
|
import com.android.camera.app.CameraAppUI;
|
import com.android.camera.app.CameraProvider;
|
import com.android.camera.app.MediaSaver;
|
import com.android.camera.app.MemoryManager;
|
import com.android.camera.app.MemoryManager.MemoryListener;
|
import com.android.camera.app.MotionManager;
|
import com.android.camera.debug.Log;
|
import com.android.camera.exif.ExifInterface;
|
import com.android.camera.exif.ExifTag;
|
import com.android.camera.exif.Rational;
|
import com.android.camera.hardware.HardwareSpec;
|
import com.android.camera.hardware.HardwareSpecImpl;
|
import com.android.camera.hardware.HeadingSensor;
|
import com.android.camera.module.ModuleController;
|
import com.android.camera.one.OneCamera;
|
import com.android.camera.one.OneCameraAccessException;
|
import com.android.camera.one.OneCameraException;
|
import com.android.camera.one.OneCameraManager;
|
import com.android.camera.one.OneCameraModule;
|
import com.android.camera.remote.RemoteCameraModule;
|
import com.android.camera.settings.CameraPictureSizesCacher;
|
import com.android.camera.settings.Keys;
|
import com.android.camera.settings.ResolutionUtil;
|
import com.android.camera.settings.SettingsManager;
|
import com.android.camera.stats.SessionStatsCollector;
|
import com.android.camera.stats.UsageStatistics;
|
import com.android.camera.ui.CountDownView;
|
import com.android.camera.ui.TouchCoordinate;
|
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.GservicesHelper;
|
import com.android.camera.util.Size;
|
import com.android.camera2.R;
|
import com.android.ex.camera2.portability.CameraAgent;
|
import com.android.ex.camera2.portability.CameraAgent.CameraAFCallback;
|
import com.android.ex.camera2.portability.CameraAgent.CameraAFMoveCallback;
|
import com.android.ex.camera2.portability.CameraAgent.CameraPictureCallback;
|
import com.android.ex.camera2.portability.CameraAgent.CameraProxy;
|
import com.android.ex.camera2.portability.CameraAgent.CameraShutterCallback;
|
import com.android.ex.camera2.portability.CameraCapabilities;
|
import com.android.ex.camera2.portability.CameraDeviceInfo.Characteristics;
|
import com.android.ex.camera2.portability.CameraSettings;
|
import com.google.common.logging.eventprotos;
|
|
import java.io.ByteArrayOutputStream;
|
import java.io.File;
|
import java.io.FileNotFoundException;
|
import java.io.FileOutputStream;
|
import java.io.IOException;
|
import java.io.OutputStream;
|
import java.lang.ref.WeakReference;
|
import java.util.ArrayList;
|
import java.util.List;
|
import java.util.Vector;
|
|
|
public class PhotoModule
|
extends CameraModule
|
implements PhotoController,
|
ModuleController,
|
MemoryListener,
|
FocusOverlayManager.Listener,
|
SettingsManager.OnSettingChangedListener,
|
RemoteCameraModule,
|
CountDownView.OnCountDownStatusListener {
|
|
private static final Log.Tag TAG = new Log.Tag("PhotoModule");
|
|
// We number the request code from 1000 to avoid collision with Gallery.
|
private static final int REQUEST_CROP = 1000;
|
|
// Messages defined for the UI thread handler.
|
private static final int MSG_FIRST_TIME_INIT = 1;
|
private static final int MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE = 2;
|
|
// The subset of parameters we need to update in setCameraParameters().
|
private static final int UPDATE_PARAM_INITIALIZE = 1;
|
private static final int UPDATE_PARAM_ZOOM = 2;
|
private static final int UPDATE_PARAM_PREFERENCE = 4;
|
private static final int UPDATE_PARAM_ALL = -1;
|
|
private static final String DEBUG_IMAGE_PREFIX = "DEBUG_";
|
|
private CameraActivity mActivity;
|
private CameraProxy mCameraDevice;
|
private int mCameraId;
|
private CameraCapabilities mCameraCapabilities;
|
private CameraSettings mCameraSettings;
|
private HardwareSpec mHardwareSpec;
|
private boolean mPaused;
|
|
private PhotoUI mUI;
|
|
// The activity is going to switch to the specified camera id. This is
|
// needed because texture copy is done in GL thread. -1 means camera is not
|
// switching.
|
protected int mPendingSwitchCameraId = -1;
|
|
// When setCameraParametersWhenIdle() is called, we accumulate the subsets
|
// needed to be updated in mUpdateSet.
|
private int mUpdateSet;
|
|
private float mZoomValue; // The current zoom ratio.
|
private int mTimerDuration;
|
/** Set when a volume button is clicked to take photo */
|
private boolean mVolumeButtonClickedFlag = false;
|
|
private boolean mFocusAreaSupported;
|
private boolean mMeteringAreaSupported;
|
private boolean mAeLockSupported;
|
private boolean mAwbLockSupported;
|
private boolean mContinuousFocusSupported;
|
|
private static final String sTempCropFilename = "crop-temp";
|
|
private boolean mFaceDetectionStarted = false;
|
|
// mCropValue and mSaveUri are used only if isImageCaptureIntent() is true.
|
private String mCropValue;
|
private Uri mSaveUri;
|
|
private Uri mDebugUri;
|
|
// We use a queue to generated names of the images to be used later
|
// when the image is ready to be saved.
|
private NamedImages mNamedImages;
|
|
private final Runnable mDoSnapRunnable = new Runnable() {
|
@Override
|
public void run() {
|
onShutterButtonClick();
|
}
|
};
|
|
/**
|
* An unpublished intent flag requesting to return as soon as capturing is
|
* completed. TODO: consider publishing by moving into MediaStore.
|
*/
|
private static final String EXTRA_QUICK_CAPTURE =
|
"android.intent.extra.quickCapture";
|
|
// The display rotation in degrees. This is only valid when mCameraState is
|
// not PREVIEW_STOPPED.
|
private int mDisplayRotation;
|
// The value for UI components like indicators.
|
private int mDisplayOrientation;
|
// The value for cameradevice.CameraSettings.setPhotoRotationDegrees.
|
private int mJpegRotation;
|
// Indicates whether we are using front camera
|
private boolean mMirror;
|
private boolean mFirstTimeInitialized;
|
private boolean mIsImageCaptureIntent;
|
|
private int mCameraState = PREVIEW_STOPPED;
|
private boolean mSnapshotOnIdle = false;
|
|
private ContentResolver mContentResolver;
|
|
private AppController mAppController;
|
private OneCameraManager mOneCameraManager;
|
|
private final PostViewPictureCallback mPostViewPictureCallback =
|
new PostViewPictureCallback();
|
private final RawPictureCallback mRawPictureCallback =
|
new RawPictureCallback();
|
private final AutoFocusCallback mAutoFocusCallback =
|
new AutoFocusCallback();
|
private final Object mAutoFocusMoveCallback =
|
ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK
|
? new AutoFocusMoveCallback()
|
: null;
|
|
private long mFocusStartTime;
|
private long mShutterCallbackTime;
|
private long mPostViewPictureCallbackTime;
|
private long mRawPictureCallbackTime;
|
private long mJpegPictureCallbackTime;
|
private long mOnResumeTime;
|
private byte[] mJpegImageData;
|
/** Touch coordinate for shutter button press. */
|
private TouchCoordinate mShutterTouchCoordinate;
|
|
|
// These latency time are for the CameraLatency test.
|
public long mAutoFocusTime;
|
public long mShutterLag;
|
public long mShutterToPictureDisplayedTime;
|
public long mPictureDisplayedToJpegCallbackTime;
|
public long mJpegCallbackFinishTime;
|
public long mCaptureStartTime;
|
|
// This handles everything about focus.
|
private FocusOverlayManager mFocusManager;
|
|
private final int mGcamModeIndex;
|
private SoundPlayer mCountdownSoundPlayer;
|
|
private CameraCapabilities.SceneMode mSceneMode;
|
|
private final Handler mHandler = new MainHandler(this);
|
|
private boolean mQuickCapture;
|
|
/** Used to detect motion. We use this to release focus lock early. */
|
private MotionManager mMotionManager;
|
|
private HeadingSensor mHeadingSensor;
|
|
/** True if all the parameters needed to start preview is ready. */
|
private boolean mCameraPreviewParamsReady = false;
|
|
private final MediaSaver.OnMediaSavedListener mOnMediaSavedListener =
|
new MediaSaver.OnMediaSavedListener() {
|
|
@Override
|
public void onMediaSaved(Uri uri) {
|
if (uri != null) {
|
mActivity.notifyNewMedia(uri);
|
} else {
|
onError();
|
}
|
}
|
};
|
|
/**
|
* Displays error dialog and allows use to enter feedback. Does not shut
|
* down the app.
|
*/
|
private void onError() {
|
mAppController.getFatalErrorHandler().onMediaStorageFailure();
|
}
|
|
private boolean mShouldResizeTo16x9 = false;
|
|
/**
|
* We keep the flash setting before entering scene modes (HDR)
|
* and restore it after HDR is off.
|
*/
|
private String mFlashModeBeforeSceneMode;
|
|
private void checkDisplayRotation() {
|
// Need to just be a no-op for the quick resume-pause scenario.
|
if (mPaused) {
|
return;
|
}
|
// Set the display orientation if display rotation has changed.
|
// Sometimes this happens when the device is held upside
|
// down and camera app is opened. Rotation animation will
|
// take some time and the rotation value we have got may be
|
// wrong. Framework does not have a callback for this now.
|
if (CameraUtil.getDisplayRotation() != mDisplayRotation) {
|
setDisplayOrientation();
|
}
|
if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
|
mHandler.postDelayed(new Runnable() {
|
@Override
|
public void run() {
|
checkDisplayRotation();
|
}
|
}, 100);
|
}
|
}
|
|
/**
|
* This Handler is used to post message back onto the main thread of the
|
* application
|
*/
|
private static class MainHandler extends Handler {
|
private final WeakReference<PhotoModule> mModule;
|
|
public MainHandler(PhotoModule module) {
|
super(Looper.getMainLooper());
|
mModule = new WeakReference<PhotoModule>(module);
|
}
|
|
@Override
|
public void handleMessage(Message msg) {
|
PhotoModule module = mModule.get();
|
if (module == null) {
|
return;
|
}
|
switch (msg.what) {
|
case MSG_FIRST_TIME_INIT: {
|
module.initializeFirstTime();
|
break;
|
}
|
|
case MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE: {
|
module.setCameraParametersWhenIdle(0);
|
break;
|
}
|
}
|
}
|
}
|
|
private void switchToGcamCapture() {
|
if (mActivity != null && mGcamModeIndex != 0) {
|
SettingsManager settingsManager = mActivity.getSettingsManager();
|
settingsManager.set(SettingsManager.SCOPE_GLOBAL,
|
Keys.KEY_CAMERA_HDR_PLUS, true);
|
|
// Disable the HDR+ button to prevent callbacks from being
|
// queued before the correct callback is attached to the button
|
// in the new module. The new module will set the enabled/disabled
|
// of this button when the module's preferred camera becomes available.
|
ButtonManager buttonManager = mActivity.getButtonManager();
|
|
buttonManager.disableButtonClick(ButtonManager.BUTTON_HDR_PLUS);
|
|
mAppController.getCameraAppUI().freezeScreenUntilPreviewReady();
|
|
// Do not post this to avoid this module switch getting interleaved with
|
// other button callbacks.
|
mActivity.onModeSelected(mGcamModeIndex);
|
|
buttonManager.enableButtonClick(ButtonManager.BUTTON_HDR_PLUS);
|
}
|
}
|
|
/**
|
* Constructs a new photo module.
|
*/
|
public PhotoModule(AppController app) {
|
super(app);
|
mGcamModeIndex = app.getAndroidContext().getResources()
|
.getInteger(R.integer.camera_mode_gcam);
|
}
|
|
@Override
|
public String getPeekAccessibilityString() {
|
return mAppController.getAndroidContext()
|
.getResources().getString(R.string.photo_accessibility_peek);
|
}
|
|
@Override
|
public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
|
mActivity = activity;
|
// TODO: Need to look at the controller interface to see if we can get
|
// rid of passing in the activity directly.
|
mAppController = mActivity;
|
|
mUI = new PhotoUI(mActivity, this, mActivity.getModuleLayoutRoot());
|
mActivity.setPreviewStatusListener(mUI);
|
|
SettingsManager settingsManager = mActivity.getSettingsManager();
|
// TODO: Move this to SettingsManager as a part of upgrade procedure.
|
// Aspect Ratio selection dialog is only shown for Nexus 4, 5 and 6.
|
if (mAppController.getCameraAppUI().shouldShowAspectRatioDialog()) {
|
// Switch to back camera to set aspect ratio.
|
settingsManager.setToDefault(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID);
|
}
|
mCameraId = settingsManager.getInteger(mAppController.getModuleScope(),
|
Keys.KEY_CAMERA_ID);
|
|
mContentResolver = mActivity.getContentResolver();
|
|
// Surface texture is from camera screen nail and startPreview needs it.
|
// This must be done before startPreview.
|
mIsImageCaptureIntent = isImageCaptureIntent();
|
mUI.setCountdownFinishedListener(this);
|
|
mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
|
mHeadingSensor = new HeadingSensor(AndroidServices.instance().provideSensorManager());
|
mCountdownSoundPlayer = new SoundPlayer(mAppController.getAndroidContext());
|
|
try {
|
mOneCameraManager = OneCameraModule.provideOneCameraManager();
|
} catch (OneCameraException e) {
|
Log.e(TAG, "Hardware manager failed to open.");
|
}
|
|
// TODO: Make this a part of app controller API.
|
View cancelButton = mActivity.findViewById(R.id.shutter_cancel_button);
|
cancelButton.setOnClickListener(new View.OnClickListener() {
|
@Override
|
public void onClick(View view) {
|
cancelCountDown();
|
}
|
});
|
}
|
|
private void cancelCountDown() {
|
if (mUI.isCountingDown()) {
|
// Cancel on-going countdown.
|
mUI.cancelCountDown();
|
}
|
mAppController.getCameraAppUI().transitionToCapture();
|
mAppController.getCameraAppUI().showModeOptions();
|
mAppController.setShutterEnabled(true);
|
}
|
|
@Override
|
public boolean isUsingBottomBar() {
|
return true;
|
}
|
|
private void initializeControlByIntent() {
|
if (mIsImageCaptureIntent) {
|
mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
|
setupCaptureParams();
|
}
|
}
|
|
private void onPreviewStarted() {
|
mAppController.onPreviewStarted();
|
mAppController.setShutterEnabled(true);
|
setCameraState(IDLE);
|
startFaceDetection();
|
}
|
|
@Override
|
public void onPreviewUIReady() {
|
Log.i(TAG, "onPreviewUIReady");
|
startPreview();
|
}
|
|
@Override
|
public void onPreviewUIDestroyed() {
|
if (mCameraDevice == null) {
|
return;
|
}
|
mCameraDevice.setPreviewTexture(null);
|
stopPreview();
|
}
|
|
@Override
|
public void startPreCaptureAnimation() {
|
mAppController.startFlashAnimation(false);
|
}
|
|
private void onCameraOpened() {
|
openCameraCommon();
|
initializeControlByIntent();
|
}
|
|
private void switchCamera() {
|
if (mPaused) {
|
return;
|
}
|
cancelCountDown();
|
|
mAppController.freezeScreenUntilPreviewReady();
|
SettingsManager settingsManager = mActivity.getSettingsManager();
|
|
Log.i(TAG, "Start to switch camera. id=" + mPendingSwitchCameraId);
|
closeCamera();
|
mCameraId = mPendingSwitchCameraId;
|
|
settingsManager.set(mAppController.getModuleScope(), Keys.KEY_CAMERA_ID, mCameraId);
|
requestCameraOpen();
|
mUI.clearFaces();
|
if (mFocusManager != null) {
|
mFocusManager.removeMessages();
|
}
|
|
mMirror = isCameraFrontFacing();
|
mFocusManager.setMirror(mMirror);
|
// Start switch camera animation. Post a message because
|
// onFrameAvailable from the old camera may already exist.
|
}
|
|
/**
|
* Uses the {@link CameraProvider} to open the currently-selected camera
|
* device, using {@link GservicesHelper} to choose between API-1 and API-2.
|
*/
|
private void requestCameraOpen() {
|
Log.v(TAG, "requestCameraOpen");
|
mActivity.getCameraProvider().requestCamera(mCameraId,
|
GservicesHelper.useCamera2ApiThroughPortabilityLayer(mActivity
|
.getContentResolver()));
|
}
|
|
private final ButtonManager.ButtonCallback mCameraCallback =
|
new ButtonManager.ButtonCallback() {
|
@Override
|
public void onStateChanged(int state) {
|
// At the time this callback is fired, the camera id
|
// has be set to the desired camera.
|
|
if (mPaused || mAppController.getCameraProvider().waitingForCamera()) {
|
return;
|
}
|
// If switching to back camera, and HDR+ is still on,
|
// switch back to gcam, otherwise handle callback normally.
|
SettingsManager settingsManager = mActivity.getSettingsManager();
|
if (Keys.isCameraBackFacing(settingsManager,
|
mAppController.getModuleScope())) {
|
if (Keys.requestsReturnToHdrPlus(settingsManager,
|
mAppController.getModuleScope())) {
|
switchToGcamCapture();
|
return;
|
}
|
}
|
|
ButtonManager buttonManager = mActivity.getButtonManager();
|
buttonManager.disableCameraButtonAndBlock();
|
|
mPendingSwitchCameraId = state;
|
|
Log.d(TAG, "Start to switch camera. cameraId=" + state);
|
// We need to keep a preview frame for the animation before
|
// releasing the camera. This will trigger
|
// onPreviewTextureCopied.
|
// TODO: Need to animate the camera switch
|
switchCamera();
|
}
|
};
|
|
private final ButtonManager.ButtonCallback mHdrPlusCallback =
|
new ButtonManager.ButtonCallback() {
|
@Override
|
public void onStateChanged(int state) {
|
SettingsManager settingsManager = mActivity.getSettingsManager();
|
if (GcamHelper.hasGcamAsSeparateModule(
|
mAppController.getCameraFeatureConfig())) {
|
// Set the camera setting to default backfacing.
|
settingsManager.setToDefault(mAppController.getModuleScope(),
|
Keys.KEY_CAMERA_ID);
|
switchToGcamCapture();
|
} else {
|
if (Keys.isHdrOn(settingsManager)) {
|
settingsManager.set(mAppController.getCameraScope(), Keys.KEY_SCENE_MODE,
|
mCameraCapabilities.getStringifier().stringify(
|
CameraCapabilities.SceneMode.HDR));
|
} else {
|
settingsManager.set(mAppController.getCameraScope(), Keys.KEY_SCENE_MODE,
|
mCameraCapabilities.getStringifier().stringify(
|
CameraCapabilities.SceneMode.AUTO));
|
}
|
updateParametersSceneMode();
|
if (mCameraDevice != null) {
|
mCameraDevice.applySettings(mCameraSettings);
|
}
|
updateSceneMode();
|
}
|
}
|
};
|
|
private final View.OnClickListener mCancelCallback = new View.OnClickListener() {
|
@Override
|
public void onClick(View v) {
|
onCaptureCancelled();
|
}
|
};
|
|
private final View.OnClickListener mDoneCallback = new View.OnClickListener() {
|
@Override
|
public void onClick(View v) {
|
onCaptureDone();
|
}
|
};
|
|
private final View.OnClickListener mRetakeCallback = new View.OnClickListener() {
|
@Override
|
public void onClick(View v) {
|
mActivity.getCameraAppUI().transitionToIntentCaptureLayout();
|
onCaptureRetake();
|
}
|
};
|
|
@Override
|
public void hardResetSettings(SettingsManager settingsManager) {
|
// PhotoModule should hard reset HDR+ to off,
|
// and HDR to off if HDR+ is supported.
|
settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR_PLUS, false);
|
if (GcamHelper.hasGcamAsSeparateModule(mAppController.getCameraFeatureConfig())) {
|
settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR, false);
|
}
|
}
|
|
@Override
|
public HardwareSpec getHardwareSpec() {
|
if (mHardwareSpec == null) {
|
mHardwareSpec = (mCameraSettings != null ?
|
new HardwareSpecImpl(getCameraProvider(), mCameraCapabilities,
|
mAppController.getCameraFeatureConfig(), isCameraFrontFacing()) : null);
|
}
|
return mHardwareSpec;
|
}
|
|
@Override
|
public CameraAppUI.BottomBarUISpec getBottomBarSpec() {
|
CameraAppUI.BottomBarUISpec bottomBarSpec = new CameraAppUI.BottomBarUISpec();
|
|
bottomBarSpec.enableCamera = true;
|
bottomBarSpec.cameraCallback = mCameraCallback;
|
bottomBarSpec.enableFlash = !mAppController.getSettingsManager()
|
.getBoolean(SettingsManager.SCOPE_GLOBAL, Keys.KEY_CAMERA_HDR);
|
bottomBarSpec.enableHdr = true;
|
bottomBarSpec.hdrCallback = mHdrPlusCallback;
|
bottomBarSpec.enableGridLines = true;
|
if (mCameraCapabilities != null) {
|
bottomBarSpec.enableExposureCompensation = true;
|
bottomBarSpec.exposureCompensationSetCallback =
|
new CameraAppUI.BottomBarUISpec.ExposureCompensationSetCallback() {
|
@Override
|
public void setExposure(int value) {
|
setExposureCompensation(value);
|
}
|
};
|
bottomBarSpec.minExposureCompensation =
|
mCameraCapabilities.getMinExposureCompensation();
|
bottomBarSpec.maxExposureCompensation =
|
mCameraCapabilities.getMaxExposureCompensation();
|
bottomBarSpec.exposureCompensationStep =
|
mCameraCapabilities.getExposureCompensationStep();
|
}
|
|
bottomBarSpec.enableSelfTimer = true;
|
bottomBarSpec.showSelfTimer = true;
|
|
if (isImageCaptureIntent()) {
|
bottomBarSpec.showCancel = true;
|
bottomBarSpec.cancelCallback = mCancelCallback;
|
bottomBarSpec.showDone = true;
|
bottomBarSpec.doneCallback = mDoneCallback;
|
bottomBarSpec.showRetake = true;
|
bottomBarSpec.retakeCallback = mRetakeCallback;
|
}
|
|
return bottomBarSpec;
|
}
|
|
// either open a new camera or switch cameras
|
private void openCameraCommon() {
|
mUI.onCameraOpened(mCameraCapabilities, mCameraSettings);
|
if (mIsImageCaptureIntent) {
|
// Set hdr plus to default: off.
|
SettingsManager settingsManager = mActivity.getSettingsManager();
|
settingsManager.setToDefault(SettingsManager.SCOPE_GLOBAL,
|
Keys.KEY_CAMERA_HDR_PLUS);
|
}
|
updateSceneMode();
|
}
|
|
@Override
|
public void updatePreviewAspectRatio(float aspectRatio) {
|
mAppController.updatePreviewAspectRatio(aspectRatio);
|
}
|
|
private void resetExposureCompensation() {
|
SettingsManager settingsManager = mActivity.getSettingsManager();
|
if (settingsManager == null) {
|
Log.e(TAG, "Settings manager is null!");
|
return;
|
}
|
settingsManager.setToDefault(mAppController.getCameraScope(),
|
Keys.KEY_EXPOSURE);
|
}
|
|
// Snapshots can only be taken after this is called. It should be called
|
// once only. We could have done these things in onCreate() but we want to
|
// make preview screen appear as soon as possible.
|
private void initializeFirstTime() {
|
if (mFirstTimeInitialized || mPaused) {
|
return;
|
}
|
|
mUI.initializeFirstTime();
|
|
// We set the listener only when both service and shutterbutton
|
// are initialized.
|
getServices().getMemoryManager().addListener(this);
|
|
mNamedImages = new NamedImages();
|
|
mFirstTimeInitialized = true;
|
addIdleHandler();
|
|
mActivity.updateStorageSpaceAndHint(null);
|
}
|
|
// If the activity is paused and resumed, this method will be called in
|
// onResume.
|
private void initializeSecondTime() {
|
getServices().getMemoryManager().addListener(this);
|
mNamedImages = new NamedImages();
|
mUI.initializeSecondTime(mCameraCapabilities, mCameraSettings);
|
}
|
|
private void addIdleHandler() {
|
MessageQueue queue = Looper.myQueue();
|
queue.addIdleHandler(new MessageQueue.IdleHandler() {
|
@Override
|
public boolean queueIdle() {
|
Storage.ensureOSXCompatible();
|
return false;
|
}
|
});
|
}
|
|
@Override
|
public void startFaceDetection() {
|
if (mFaceDetectionStarted || mCameraDevice == null) {
|
return;
|
}
|
if (mCameraCapabilities.getMaxNumOfFacesSupported() > 0) {
|
mFaceDetectionStarted = true;
|
mUI.onStartFaceDetection(mDisplayOrientation, isCameraFrontFacing());
|
mCameraDevice.setFaceDetectionCallback(mHandler, mUI);
|
mCameraDevice.startFaceDetection();
|
SessionStatsCollector.instance().faceScanActive(true);
|
}
|
}
|
|
@Override
|
public void stopFaceDetection() {
|
if (!mFaceDetectionStarted || mCameraDevice == null) {
|
return;
|
}
|
if (mCameraCapabilities.getMaxNumOfFacesSupported() > 0) {
|
mFaceDetectionStarted = false;
|
mCameraDevice.setFaceDetectionCallback(null, null);
|
mCameraDevice.stopFaceDetection();
|
mUI.clearFaces();
|
SessionStatsCollector.instance().faceScanActive(false);
|
}
|
}
|
|
private final class ShutterCallback
|
implements CameraShutterCallback {
|
|
private final boolean mNeedsAnimation;
|
|
public ShutterCallback(boolean needsAnimation) {
|
mNeedsAnimation = needsAnimation;
|
}
|
|
@Override
|
public void onShutter(CameraProxy camera) {
|
mShutterCallbackTime = System.currentTimeMillis();
|
mShutterLag = mShutterCallbackTime - mCaptureStartTime;
|
Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
|
if (mNeedsAnimation) {
|
mActivity.runOnUiThread(new Runnable() {
|
@Override
|
public void run() {
|
animateAfterShutter();
|
}
|
});
|
}
|
}
|
}
|
|
private final class PostViewPictureCallback
|
implements CameraPictureCallback {
|
@Override
|
public void onPictureTaken(byte[] data, CameraProxy camera) {
|
mPostViewPictureCallbackTime = System.currentTimeMillis();
|
Log.v(TAG, "mShutterToPostViewCallbackTime = "
|
+ (mPostViewPictureCallbackTime - mShutterCallbackTime)
|
+ "ms");
|
}
|
}
|
|
private final class RawPictureCallback
|
implements CameraPictureCallback {
|
@Override
|
public void onPictureTaken(byte[] rawData, CameraProxy camera) {
|
mRawPictureCallbackTime = System.currentTimeMillis();
|
Log.v(TAG, "mShutterToRawCallbackTime = "
|
+ (mRawPictureCallbackTime - mShutterCallbackTime) + "ms");
|
}
|
}
|
|
private static class ResizeBundle {
|
byte[] jpegData;
|
float targetAspectRatio;
|
ExifInterface exif;
|
}
|
|
/**
|
* @return Cropped image if the target aspect ratio is larger than the jpeg
|
* aspect ratio on the long axis. The original jpeg otherwise.
|
*/
|
private ResizeBundle cropJpegDataToAspectRatio(ResizeBundle dataBundle) {
|
|
final byte[] jpegData = dataBundle.jpegData;
|
final ExifInterface exif = dataBundle.exif;
|
float targetAspectRatio = dataBundle.targetAspectRatio;
|
|
Bitmap original = BitmapFactory.decodeByteArray(jpegData, 0, jpegData.length);
|
int originalWidth = original.getWidth();
|
int originalHeight = original.getHeight();
|
int newWidth;
|
int newHeight;
|
|
if (originalWidth > originalHeight) {
|
newHeight = (int) (originalWidth / targetAspectRatio);
|
newWidth = originalWidth;
|
} else {
|
newWidth = (int) (originalHeight / targetAspectRatio);
|
newHeight = originalHeight;
|
}
|
int xOffset = (originalWidth - newWidth)/2;
|
int yOffset = (originalHeight - newHeight)/2;
|
|
if (xOffset < 0 || yOffset < 0) {
|
return dataBundle;
|
}
|
|
Bitmap resized = Bitmap.createBitmap(original,xOffset,yOffset,newWidth, newHeight);
|
exif.setTagValue(ExifInterface.TAG_PIXEL_X_DIMENSION, new Integer(newWidth));
|
exif.setTagValue(ExifInterface.TAG_PIXEL_Y_DIMENSION, new Integer(newHeight));
|
|
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
|
resized.compress(Bitmap.CompressFormat.JPEG, 90, stream);
|
dataBundle.jpegData = stream.toByteArray();
|
return dataBundle;
|
}
|
|
private final class JpegPictureCallback
|
implements CameraPictureCallback {
|
Location mLocation;
|
|
public JpegPictureCallback(Location loc) {
|
mLocation = loc;
|
}
|
|
@Override
|
public void onPictureTaken(final byte[] originalJpegData, final CameraProxy camera) {
|
Log.i(TAG, "onPictureTaken");
|
mAppController.setShutterEnabled(true);
|
if (mPaused) {
|
return;
|
}
|
if (mIsImageCaptureIntent) {
|
stopPreview();
|
}
|
if (mSceneMode == CameraCapabilities.SceneMode.HDR) {
|
mUI.setSwipingEnabled(true);
|
}
|
|
mJpegPictureCallbackTime = System.currentTimeMillis();
|
// If postview callback has arrived, the captured image is displayed
|
// in postview callback. If not, the captured image is displayed in
|
// raw picture callback.
|
if (mPostViewPictureCallbackTime != 0) {
|
mShutterToPictureDisplayedTime =
|
mPostViewPictureCallbackTime - mShutterCallbackTime;
|
mPictureDisplayedToJpegCallbackTime =
|
mJpegPictureCallbackTime - mPostViewPictureCallbackTime;
|
} else {
|
mShutterToPictureDisplayedTime =
|
mRawPictureCallbackTime - mShutterCallbackTime;
|
mPictureDisplayedToJpegCallbackTime =
|
mJpegPictureCallbackTime - mRawPictureCallbackTime;
|
}
|
Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = "
|
+ mPictureDisplayedToJpegCallbackTime + "ms");
|
|
if (!mIsImageCaptureIntent) {
|
setupPreview();
|
}
|
|
long now = System.currentTimeMillis();
|
mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
|
Log.v(TAG, "mJpegCallbackFinishTime = " + mJpegCallbackFinishTime + "ms");
|
mJpegPictureCallbackTime = 0;
|
|
final ExifInterface exif = Exif.getExif(originalJpegData);
|
final NamedEntity name = mNamedImages.getNextNameEntity();
|
if (mShouldResizeTo16x9) {
|
final ResizeBundle dataBundle = new ResizeBundle();
|
dataBundle.jpegData = originalJpegData;
|
dataBundle.targetAspectRatio = ResolutionUtil.NEXUS_5_LARGE_16_BY_9_ASPECT_RATIO;
|
dataBundle.exif = exif;
|
new AsyncTask<ResizeBundle, Void, ResizeBundle>() {
|
|
@Override
|
protected ResizeBundle doInBackground(ResizeBundle... resizeBundles) {
|
return cropJpegDataToAspectRatio(resizeBundles[0]);
|
}
|
|
@Override
|
protected void onPostExecute(ResizeBundle result) {
|
saveFinalPhoto(result.jpegData, name, result.exif, camera);
|
}
|
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, dataBundle);
|
|
} else {
|
saveFinalPhoto(originalJpegData, name, exif, camera);
|
}
|
}
|
|
void saveFinalPhoto(final byte[] jpegData, NamedEntity name, final ExifInterface exif,
|
CameraProxy camera) {
|
int orientation = Exif.getOrientation(exif);
|
|
float zoomValue = 1.0f;
|
if (mCameraCapabilities.supports(CameraCapabilities.Feature.ZOOM)) {
|
zoomValue = mCameraSettings.getCurrentZoomRatio();
|
}
|
boolean hdrOn = CameraCapabilities.SceneMode.HDR == mSceneMode;
|
String flashSetting =
|
mActivity.getSettingsManager().getString(mAppController.getCameraScope(),
|
Keys.KEY_FLASH_MODE);
|
boolean gridLinesOn = Keys.areGridLinesOn(mActivity.getSettingsManager());
|
UsageStatistics.instance().photoCaptureDoneEvent(
|
eventprotos.NavigationChange.Mode.PHOTO_CAPTURE,
|
name.title + ".jpg", exif,
|
isCameraFrontFacing(), hdrOn, zoomValue, flashSetting, gridLinesOn,
|
(float) mTimerDuration, null, mShutterTouchCoordinate, mVolumeButtonClickedFlag,
|
null, null, null);
|
mShutterTouchCoordinate = null;
|
mVolumeButtonClickedFlag = false;
|
|
if (!mIsImageCaptureIntent) {
|
// Calculate the width and the height of the jpeg.
|
Integer exifWidth = exif.getTagIntValue(ExifInterface.TAG_PIXEL_X_DIMENSION);
|
Integer exifHeight = exif.getTagIntValue(ExifInterface.TAG_PIXEL_Y_DIMENSION);
|
int width, height;
|
if (mShouldResizeTo16x9 && exifWidth != null && exifHeight != null) {
|
width = exifWidth;
|
height = exifHeight;
|
} else {
|
Size s = new Size(mCameraSettings.getCurrentPhotoSize());
|
if ((mJpegRotation + orientation) % 180 == 0) {
|
width = s.width();
|
height = s.height();
|
} else {
|
width = s.height();
|
height = s.width();
|
}
|
}
|
String title = (name == null) ? null : name.title;
|
long date = (name == null) ? -1 : name.date;
|
|
// Handle debug mode outputs
|
if (mDebugUri != null) {
|
// If using a debug uri, save jpeg there.
|
saveToDebugUri(jpegData);
|
|
// Adjust the title of the debug image shown in mediastore.
|
if (title != null) {
|
title = DEBUG_IMAGE_PREFIX + title;
|
}
|
}
|
|
if (title == null) {
|
Log.e(TAG, "Unbalanced name/data pair");
|
} else {
|
if (date == -1) {
|
date = mCaptureStartTime;
|
}
|
int heading = mHeadingSensor.getCurrentHeading();
|
if (heading != HeadingSensor.INVALID_HEADING) {
|
// heading direction has been updated by the sensor.
|
ExifTag directionRefTag = exif.buildTag(
|
ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
|
ExifInterface.GpsTrackRef.MAGNETIC_DIRECTION);
|
ExifTag directionTag = exif.buildTag(
|
ExifInterface.TAG_GPS_IMG_DIRECTION,
|
new Rational(heading, 1));
|
exif.setTag(directionRefTag);
|
exif.setTag(directionTag);
|
}
|
getServices().getMediaSaver().addImage(
|
jpegData, title, date, mLocation, width, height,
|
orientation, exif, mOnMediaSavedListener);
|
}
|
// Animate capture with real jpeg data instead of a preview
|
// frame.
|
mUI.animateCapture(jpegData, orientation, mMirror);
|
} else {
|
mJpegImageData = jpegData;
|
if (!mQuickCapture) {
|
Log.v(TAG, "showing UI");
|
mUI.showCapturedImageForReview(jpegData, orientation, mMirror);
|
} else {
|
onCaptureDone();
|
}
|
}
|
|
// Send the taken photo to remote shutter listeners, if any are
|
// registered.
|
getServices().getRemoteShutterListener().onPictureTaken(jpegData);
|
|
// Check this in advance of each shot so we don't add to shutter
|
// latency. It's true that someone else could write to the SD card
|
// in the mean time and fill it, but that could have happened
|
// between the shutter press and saving the JPEG too.
|
mActivity.updateStorageSpaceAndHint(null);
|
}
|
}
|
|
private final class AutoFocusCallback implements CameraAFCallback {
|
@Override
|
public void onAutoFocus(boolean focused, CameraProxy camera) {
|
SessionStatsCollector.instance().autofocusResult(focused);
|
if (mPaused) {
|
return;
|
}
|
|
mAutoFocusTime = System.currentTimeMillis() - mFocusStartTime;
|
Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms focused = "+focused);
|
setCameraState(IDLE);
|
mFocusManager.onAutoFocus(focused, false);
|
}
|
}
|
|
private final class AutoFocusMoveCallback
|
implements CameraAFMoveCallback {
|
@Override
|
public void onAutoFocusMoving(
|
boolean moving, CameraProxy camera) {
|
mFocusManager.onAutoFocusMoving(moving);
|
SessionStatsCollector.instance().autofocusMoving(moving);
|
}
|
}
|
|
/**
|
* This class is just a thread-safe queue for name,date holder objects.
|
*/
|
public static class NamedImages {
|
private final Vector<NamedEntity> mQueue;
|
|
public NamedImages() {
|
mQueue = new Vector<NamedEntity>();
|
}
|
|
public void nameNewImage(long date) {
|
NamedEntity r = new NamedEntity();
|
r.title = CameraUtil.instance().createJpegName(date);
|
r.date = date;
|
mQueue.add(r);
|
}
|
|
public NamedEntity getNextNameEntity() {
|
synchronized (mQueue) {
|
if (!mQueue.isEmpty()) {
|
return mQueue.remove(0);
|
}
|
}
|
return null;
|
}
|
|
public static class NamedEntity {
|
public String title;
|
public long date;
|
}
|
}
|
|
private void setCameraState(int state) {
|
mCameraState = state;
|
switch (state) {
|
case PREVIEW_STOPPED:
|
case SNAPSHOT_IN_PROGRESS:
|
case SWITCHING_CAMERA:
|
// TODO: Tell app UI to disable swipe
|
break;
|
case PhotoController.IDLE:
|
// TODO: Tell app UI to enable swipe
|
break;
|
}
|
}
|
|
private void animateAfterShutter() {
|
// Only animate when in full screen capture mode
|
// i.e. If monkey/a user swipes to the gallery during picture taking,
|
// don't show animation
|
if (!mIsImageCaptureIntent) {
|
mUI.animateFlash();
|
}
|
}
|
|
@Override
|
public boolean capture() {
|
Log.i(TAG, "capture");
|
// If we are already in the middle of taking a snapshot or the image
|
// save request is full then ignore.
|
if (mCameraDevice == null || mCameraState == SNAPSHOT_IN_PROGRESS
|
|| mCameraState == SWITCHING_CAMERA) {
|
return false;
|
}
|
setCameraState(SNAPSHOT_IN_PROGRESS);
|
|
mCaptureStartTime = System.currentTimeMillis();
|
|
mPostViewPictureCallbackTime = 0;
|
mJpegImageData = null;
|
|
final boolean animateBefore = (mSceneMode == CameraCapabilities.SceneMode.HDR);
|
|
if (animateBefore) {
|
animateAfterShutter();
|
}
|
|
Location loc = mActivity.getLocationManager().getCurrentLocation();
|
CameraUtil.setGpsParameters(mCameraSettings, loc);
|
mCameraDevice.applySettings(mCameraSettings);
|
|
// Set JPEG orientation. Even if screen UI is locked in portrait, camera orientation should
|
// still match device orientation (e.g., users should always get landscape photos while
|
// capturing by putting device in landscape.)
|
Characteristics info = mActivity.getCameraProvider().getCharacteristics(mCameraId);
|
int sensorOrientation = info.getSensorOrientation();
|
int deviceOrientation =
|
mAppController.getOrientationManager().getDeviceOrientation().getDegrees();
|
boolean isFrontCamera = info.isFacingFront();
|
mJpegRotation =
|
CameraUtil.getImageRotation(sensorOrientation, deviceOrientation, isFrontCamera);
|
mCameraDevice.setJpegOrientation(mJpegRotation);
|
|
mCameraDevice.takePicture(mHandler,
|
new ShutterCallback(!animateBefore),
|
mRawPictureCallback, mPostViewPictureCallback,
|
new JpegPictureCallback(loc));
|
|
mNamedImages.nameNewImage(mCaptureStartTime);
|
|
mFaceDetectionStarted = false;
|
return true;
|
}
|
|
@Override
|
public void setFocusParameters() {
|
setCameraParameters(UPDATE_PARAM_PREFERENCE);
|
}
|
|
private void updateSceneMode() {
|
// If scene mode is set, we cannot set flash mode, white balance, and
|
// focus mode, instead, we read it from driver. Some devices don't have
|
// any scene modes, so we must check both NO_SCENE_MODE in addition to
|
// AUTO to check where there is no actual scene mode set.
|
if (!(CameraCapabilities.SceneMode.AUTO == mSceneMode ||
|
CameraCapabilities.SceneMode.NO_SCENE_MODE == mSceneMode)) {
|
overrideCameraSettings(mCameraSettings.getCurrentFlashMode(),
|
mCameraSettings.getCurrentFocusMode());
|
}
|
}
|
|
private void overrideCameraSettings(CameraCapabilities.FlashMode flashMode,
|
CameraCapabilities.FocusMode focusMode) {
|
CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
|
SettingsManager settingsManager = mActivity.getSettingsManager();
|
if ((flashMode != null) && (!CameraCapabilities.FlashMode.NO_FLASH.equals(flashMode))) {
|
String flashModeString = stringifier.stringify(flashMode);
|
Log.v(TAG, "override flash setting to: " + flashModeString);
|
settingsManager.set(mAppController.getCameraScope(), Keys.KEY_FLASH_MODE,
|
flashModeString);
|
} else {
|
Log.v(TAG, "skip setting flash mode on override due to NO_FLASH");
|
}
|
if (focusMode != null) {
|
String focusModeString = stringifier.stringify(focusMode);
|
Log.v(TAG, "override focus setting to: " + focusModeString);
|
settingsManager.set(mAppController.getCameraScope(), Keys.KEY_FOCUS_MODE,
|
focusModeString);
|
}
|
}
|
|
@Override
|
public void onCameraAvailable(CameraProxy cameraProxy) {
|
Log.i(TAG, "onCameraAvailable");
|
if (mPaused) {
|
return;
|
}
|
mCameraDevice = cameraProxy;
|
|
initializeCapabilities();
|
// mCameraCapabilities is guaranteed to initialized at this point.
|
mAppController.getCameraAppUI().showAccessibilityZoomUI(
|
mCameraCapabilities.getMaxZoomRatio());
|
|
|
// Reset zoom value index.
|
mZoomValue = 1.0f;
|
if (mFocusManager == null) {
|
initializeFocusManager();
|
}
|
mFocusManager.updateCapabilities(mCameraCapabilities);
|
|
// Do camera parameter dependent initialization.
|
mCameraSettings = mCameraDevice.getSettings();
|
// Set a default flash mode and focus mode
|
if (mCameraSettings.getCurrentFlashMode() == null) {
|
mCameraSettings.setFlashMode(CameraCapabilities.FlashMode.NO_FLASH);
|
}
|
if (mCameraSettings.getCurrentFocusMode() == null) {
|
mCameraSettings.setFocusMode(CameraCapabilities.FocusMode.AUTO);
|
}
|
|
setCameraParameters(UPDATE_PARAM_ALL);
|
// Set a listener which updates camera parameters based
|
// on changed settings.
|
SettingsManager settingsManager = mActivity.getSettingsManager();
|
settingsManager.addListener(this);
|
mCameraPreviewParamsReady = true;
|
|
startPreview();
|
|
onCameraOpened();
|
|
mHardwareSpec = new HardwareSpecImpl(getCameraProvider(), mCameraCapabilities,
|
mAppController.getCameraFeatureConfig(), isCameraFrontFacing());
|
|
ButtonManager buttonManager = mActivity.getButtonManager();
|
buttonManager.enableCameraButton();
|
}
|
|
@Override
|
public void onCaptureCancelled() {
|
mActivity.setResultEx(Activity.RESULT_CANCELED, new Intent());
|
mActivity.finish();
|
}
|
|
@Override
|
public void onCaptureRetake() {
|
Log.i(TAG, "onCaptureRetake");
|
if (mPaused) {
|
return;
|
}
|
mUI.hidePostCaptureAlert();
|
mUI.hideIntentReviewImageView();
|
setupPreview();
|
}
|
|
@Override
|
public void onCaptureDone() {
|
Log.i(TAG, "onCaptureDone");
|
if (mPaused) {
|
return;
|
}
|
|
byte[] data = mJpegImageData;
|
|
if (mCropValue == null) {
|
// First handle the no crop case -- just return the value. If the
|
// caller specifies a "save uri" then write the data to its
|
// stream. Otherwise, pass back a scaled down version of the bitmap
|
// directly in the extras.
|
if (mSaveUri != null) {
|
OutputStream outputStream = null;
|
try {
|
outputStream = mContentResolver.openOutputStream(mSaveUri);
|
outputStream.write(data);
|
outputStream.close();
|
|
Log.v(TAG, "saved result to URI: " + mSaveUri);
|
mActivity.setResultEx(Activity.RESULT_OK);
|
mActivity.finish();
|
} catch (IOException ex) {
|
onError();
|
} finally {
|
CameraUtil.closeSilently(outputStream);
|
}
|
} else {
|
ExifInterface exif = Exif.getExif(data);
|
int orientation = Exif.getOrientation(exif);
|
Bitmap bitmap = CameraUtil.makeBitmap(data, 50 * 1024);
|
bitmap = CameraUtil.rotate(bitmap, orientation);
|
Log.v(TAG, "inlined bitmap into capture intent result");
|
mActivity.setResultEx(Activity.RESULT_OK,
|
new Intent("inline-data").putExtra("data", bitmap));
|
mActivity.finish();
|
}
|
} else {
|
// Save the image to a temp file and invoke the cropper
|
Uri tempUri = null;
|
FileOutputStream tempStream = null;
|
try {
|
File path = mActivity.getFileStreamPath(sTempCropFilename);
|
path.delete();
|
tempStream = mActivity.openFileOutput(sTempCropFilename, 0);
|
tempStream.write(data);
|
tempStream.close();
|
tempUri = Uri.fromFile(path);
|
Log.v(TAG, "wrote temp file for cropping to: " + sTempCropFilename);
|
} catch (FileNotFoundException ex) {
|
Log.w(TAG, "error writing temp cropping file to: " + sTempCropFilename, ex);
|
mActivity.setResultEx(Activity.RESULT_CANCELED);
|
onError();
|
return;
|
} catch (IOException ex) {
|
Log.w(TAG, "error writing temp cropping file to: " + sTempCropFilename, ex);
|
mActivity.setResultEx(Activity.RESULT_CANCELED);
|
onError();
|
return;
|
} finally {
|
CameraUtil.closeSilently(tempStream);
|
}
|
|
Bundle newExtras = new Bundle();
|
if (mCropValue.equals("circle")) {
|
newExtras.putString("circleCrop", "true");
|
}
|
if (mSaveUri != null) {
|
Log.v(TAG, "setting output of cropped file to: " + mSaveUri);
|
newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, mSaveUri);
|
} else {
|
newExtras.putBoolean(CameraUtil.KEY_RETURN_DATA, true);
|
}
|
if (mActivity.isSecureCamera()) {
|
newExtras.putBoolean(CameraUtil.KEY_SHOW_WHEN_LOCKED, true);
|
}
|
|
// TODO: Share this constant.
|
final String CROP_ACTION = "com.android.camera.action.CROP";
|
Intent cropIntent = new Intent(CROP_ACTION);
|
|
cropIntent.setData(tempUri);
|
cropIntent.putExtras(newExtras);
|
Log.v(TAG, "starting CROP intent for capture");
|
mActivity.startActivityForResult(cropIntent, REQUEST_CROP);
|
}
|
}
|
|
@Override
|
public void onShutterCoordinate(TouchCoordinate coord) {
|
mShutterTouchCoordinate = coord;
|
}
|
|
@Override
|
public void onShutterButtonFocus(boolean pressed) {
|
// Do nothing. We don't support half-press to focus anymore.
|
}
|
|
@Override
|
public void onShutterButtonClick() {
|
if (mPaused || (mCameraState == SWITCHING_CAMERA)
|
|| (mCameraState == PREVIEW_STOPPED)
|
|| !mAppController.isShutterEnabled()) {
|
mVolumeButtonClickedFlag = false;
|
return;
|
}
|
|
// Do not take the picture if there is not enough storage.
|
if (mActivity.getStorageSpaceBytes() <= Storage.LOW_STORAGE_THRESHOLD_BYTES) {
|
Log.i(TAG, "Not enough space or storage not ready. remaining="
|
+ mActivity.getStorageSpaceBytes());
|
mVolumeButtonClickedFlag = false;
|
return;
|
}
|
Log.d(TAG, "onShutterButtonClick: mCameraState=" + mCameraState +
|
" mVolumeButtonClickedFlag=" + mVolumeButtonClickedFlag);
|
|
mAppController.setShutterEnabled(false);
|
|
int countDownDuration = mActivity.getSettingsManager()
|
.getInteger(SettingsManager.SCOPE_GLOBAL, Keys.KEY_COUNTDOWN_DURATION);
|
mTimerDuration = countDownDuration;
|
if (countDownDuration > 0) {
|
// Start count down.
|
mAppController.getCameraAppUI().transitionToCancel();
|
mAppController.getCameraAppUI().hideModeOptions();
|
mUI.startCountdown(countDownDuration);
|
return;
|
} else {
|
focusAndCapture();
|
}
|
}
|
|
private void focusAndCapture() {
|
if (mSceneMode == CameraCapabilities.SceneMode.HDR) {
|
mUI.setSwipingEnabled(false);
|
}
|
// If the user wants to do a snapshot while the previous one is still
|
// in progress, remember the fact and do it after we finish the previous
|
// one and re-start the preview. Snapshot in progress also includes the
|
// state that autofocus is focusing and a picture will be taken when
|
// focus callback arrives.
|
if ((mFocusManager.isFocusingSnapOnFinish() || mCameraState == SNAPSHOT_IN_PROGRESS)) {
|
if (!mIsImageCaptureIntent) {
|
mSnapshotOnIdle = true;
|
}
|
return;
|
}
|
|
mSnapshotOnIdle = false;
|
mFocusManager.focusAndCapture(mCameraSettings.getCurrentFocusMode());
|
}
|
|
@Override
|
public void onRemainingSecondsChanged(int remainingSeconds) {
|
if (remainingSeconds == 1) {
|
mCountdownSoundPlayer.play(R.raw.timer_final_second, 0.6f);
|
} else if (remainingSeconds == 2 || remainingSeconds == 3) {
|
mCountdownSoundPlayer.play(R.raw.timer_increment, 0.6f);
|
}
|
}
|
|
@Override
|
public void onCountDownFinished() {
|
mAppController.getCameraAppUI().transitionToCapture();
|
mAppController.getCameraAppUI().showModeOptions();
|
if (mPaused) {
|
return;
|
}
|
focusAndCapture();
|
}
|
|
@Override
|
public void resume() {
|
mPaused = false;
|
|
mCountdownSoundPlayer.loadSound(R.raw.timer_final_second);
|
mCountdownSoundPlayer.loadSound(R.raw.timer_increment);
|
if (mFocusManager != null) {
|
// If camera is not open when resume is called, focus manager will
|
// not be initialized yet, in which case it will start listening to
|
// preview area size change later in the initialization.
|
mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
|
}
|
mAppController.addPreviewAreaSizeChangedListener(mUI);
|
|
CameraProvider camProvider = mActivity.getCameraProvider();
|
if (camProvider == null) {
|
// No camera provider, the Activity is destroyed already.
|
return;
|
}
|
|
// Close the review UI if it's currently visible.
|
mUI.hidePostCaptureAlert();
|
mUI.hideIntentReviewImageView();
|
|
requestCameraOpen();
|
|
mJpegPictureCallbackTime = 0;
|
mZoomValue = 1.0f;
|
|
mOnResumeTime = SystemClock.uptimeMillis();
|
checkDisplayRotation();
|
|
// If first time initialization is not finished, put it in the
|
// message queue.
|
if (!mFirstTimeInitialized) {
|
mHandler.sendEmptyMessage(MSG_FIRST_TIME_INIT);
|
} else {
|
initializeSecondTime();
|
}
|
|
mHeadingSensor.activate();
|
|
getServices().getRemoteShutterListener().onModuleReady(this);
|
SessionStatsCollector.instance().sessionActive(true);
|
}
|
|
/**
|
* @return Whether the currently active camera is front-facing.
|
*/
|
private boolean isCameraFrontFacing() {
|
return mAppController.getCameraProvider().getCharacteristics(mCameraId)
|
.isFacingFront();
|
}
|
|
/**
|
* The focus manager is the first UI related element to get initialized, and
|
* it requires the RenderOverlay, so initialize it here
|
*/
|
private void initializeFocusManager() {
|
// Create FocusManager object. startPreview needs it.
|
// if mFocusManager not null, reuse it
|
// otherwise create a new instance
|
if (mFocusManager != null) {
|
mFocusManager.removeMessages();
|
} else {
|
mMirror = isCameraFrontFacing();
|
String[] defaultFocusModesStrings = mActivity.getResources().getStringArray(
|
R.array.pref_camera_focusmode_default_array);
|
ArrayList<CameraCapabilities.FocusMode> defaultFocusModes =
|
new ArrayList<CameraCapabilities.FocusMode>();
|
CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
|
for (String modeString : defaultFocusModesStrings) {
|
CameraCapabilities.FocusMode mode = stringifier.focusModeFromString(modeString);
|
if (mode != null) {
|
defaultFocusModes.add(mode);
|
}
|
}
|
mFocusManager =
|
new FocusOverlayManager(mAppController, defaultFocusModes,
|
mCameraCapabilities, this, mMirror, mActivity.getMainLooper(),
|
mUI.getFocusRing());
|
mMotionManager = getServices().getMotionManager();
|
if (mMotionManager != null) {
|
mMotionManager.addListener(mFocusManager);
|
}
|
}
|
mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
|
}
|
|
/**
|
* @return Whether we are resuming from within the lockscreen.
|
*/
|
private boolean isResumeFromLockscreen() {
|
String action = mActivity.getIntent().getAction();
|
return (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(action)
|
|| MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action));
|
}
|
|
@Override
|
public void pause() {
|
Log.v(TAG, "pause");
|
mPaused = true;
|
getServices().getRemoteShutterListener().onModuleExit();
|
SessionStatsCollector.instance().sessionActive(false);
|
|
mHeadingSensor.deactivate();
|
|
// Reset the focus first. Camera CTS does not guarantee that
|
// cancelAutoFocus is allowed after preview stops.
|
if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
|
mCameraDevice.cancelAutoFocus();
|
}
|
|
// If the camera has not been opened asynchronously yet,
|
// and startPreview hasn't been called, then this is a no-op.
|
// (e.g. onResume -> onPause -> onResume).
|
stopPreview();
|
cancelCountDown();
|
mCountdownSoundPlayer.unloadSound(R.raw.timer_final_second);
|
mCountdownSoundPlayer.unloadSound(R.raw.timer_increment);
|
|
mNamedImages = null;
|
// If we are in an image capture intent and has taken
|
// a picture, we just clear it in onPause.
|
mJpegImageData = null;
|
|
// Remove the messages and runnables in the queue.
|
mHandler.removeCallbacksAndMessages(null);
|
|
if (mMotionManager != null) {
|
mMotionManager.removeListener(mFocusManager);
|
mMotionManager = null;
|
}
|
|
closeCamera();
|
mActivity.enableKeepScreenOn(false);
|
mUI.onPause();
|
|
mPendingSwitchCameraId = -1;
|
if (mFocusManager != null) {
|
mFocusManager.removeMessages();
|
}
|
getServices().getMemoryManager().removeListener(this);
|
mAppController.removePreviewAreaSizeChangedListener(mFocusManager);
|
mAppController.removePreviewAreaSizeChangedListener(mUI);
|
|
SettingsManager settingsManager = mActivity.getSettingsManager();
|
settingsManager.removeListener(this);
|
}
|
|
@Override
|
public void destroy() {
|
mCountdownSoundPlayer.release();
|
}
|
|
@Override
|
public void onLayoutOrientationChanged(boolean isLandscape) {
|
setDisplayOrientation();
|
}
|
|
@Override
|
public void updateCameraOrientation() {
|
if (mDisplayRotation != CameraUtil.getDisplayRotation()) {
|
setDisplayOrientation();
|
}
|
}
|
|
private boolean canTakePicture() {
|
return isCameraIdle()
|
&& (mActivity.getStorageSpaceBytes() > Storage.LOW_STORAGE_THRESHOLD_BYTES);
|
}
|
|
@Override
|
public void autoFocus() {
|
if (mCameraDevice == null) {
|
return;
|
}
|
Log.v(TAG,"Starting auto focus");
|
mFocusStartTime = System.currentTimeMillis();
|
mCameraDevice.autoFocus(mHandler, mAutoFocusCallback);
|
SessionStatsCollector.instance().autofocusManualTrigger();
|
setCameraState(FOCUSING);
|
}
|
|
@Override
|
public void cancelAutoFocus() {
|
if (mCameraDevice == null) {
|
return;
|
}
|
mCameraDevice.cancelAutoFocus();
|
setCameraState(IDLE);
|
setCameraParameters(UPDATE_PARAM_PREFERENCE);
|
}
|
|
@Override
|
public void onSingleTapUp(View view, int x, int y) {
|
if (mPaused || mCameraDevice == null || !mFirstTimeInitialized
|
|| mCameraState == SNAPSHOT_IN_PROGRESS
|
|| mCameraState == SWITCHING_CAMERA
|
|| mCameraState == PREVIEW_STOPPED) {
|
return;
|
}
|
|
// Check if metering area or focus area is supported.
|
if (!mFocusAreaSupported && !mMeteringAreaSupported) {
|
return;
|
}
|
mFocusManager.onSingleTapUp(x, y);
|
}
|
|
@Override
|
public boolean onBackPressed() {
|
return mUI.onBackPressed();
|
}
|
|
@Override
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
switch (keyCode) {
|
case KeyEvent.KEYCODE_VOLUME_UP:
|
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
case KeyEvent.KEYCODE_FOCUS:
|
if (/* TODO: mActivity.isInCameraApp() && */mFirstTimeInitialized &&
|
!mActivity.getCameraAppUI().isInIntentReview()) {
|
if (event.getRepeatCount() == 0) {
|
onShutterButtonFocus(true);
|
}
|
return true;
|
}
|
return false;
|
case KeyEvent.KEYCODE_CAMERA:
|
if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
|
onShutterButtonClick();
|
}
|
return true;
|
case KeyEvent.KEYCODE_DPAD_CENTER:
|
// If we get a dpad center event without any focused view, move
|
// the focus to the shutter button and press it.
|
if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
|
// Start auto-focus immediately to reduce shutter lag. After
|
// the shutter button gets the focus, onShutterButtonFocus()
|
// will be called again but it is fine.
|
onShutterButtonFocus(true);
|
}
|
return true;
|
}
|
return false;
|
}
|
|
@Override
|
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
switch (keyCode) {
|
case KeyEvent.KEYCODE_VOLUME_UP:
|
case KeyEvent.KEYCODE_VOLUME_DOWN:
|
if (/* mActivity.isInCameraApp() && */mFirstTimeInitialized &&
|
!mActivity.getCameraAppUI().isInIntentReview()) {
|
if (mUI.isCountingDown()) {
|
cancelCountDown();
|
} else {
|
mVolumeButtonClickedFlag = true;
|
onShutterButtonClick();
|
}
|
return true;
|
}
|
return false;
|
case KeyEvent.KEYCODE_FOCUS:
|
if (mFirstTimeInitialized) {
|
onShutterButtonFocus(false);
|
}
|
return true;
|
}
|
return false;
|
}
|
|
private void closeCamera() {
|
if (mCameraDevice != null) {
|
stopFaceDetection();
|
mCameraDevice.setZoomChangeListener(null);
|
mCameraDevice.setFaceDetectionCallback(null, null);
|
|
mFaceDetectionStarted = false;
|
mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId());
|
mCameraDevice = null;
|
setCameraState(PREVIEW_STOPPED);
|
mFocusManager.onCameraReleased();
|
}
|
}
|
|
private void setDisplayOrientation() {
|
mDisplayRotation = CameraUtil.getDisplayRotation();
|
Characteristics info =
|
mActivity.getCameraProvider().getCharacteristics(mCameraId);
|
mDisplayOrientation = info.getPreviewOrientation(mDisplayRotation);
|
mUI.setDisplayOrientation(mDisplayOrientation);
|
if (mFocusManager != null) {
|
mFocusManager.setDisplayOrientation(mDisplayOrientation);
|
}
|
// Change the camera display orientation
|
if (mCameraDevice != null) {
|
mCameraDevice.setDisplayOrientation(mDisplayRotation);
|
}
|
Log.v(TAG, "setDisplayOrientation (screen:preview) " +
|
mDisplayRotation + ":" + mDisplayOrientation);
|
}
|
|
/** Only called by UI thread. */
|
private void setupPreview() {
|
Log.i(TAG, "setupPreview");
|
mFocusManager.resetTouchFocus();
|
startPreview();
|
}
|
|
/**
|
* Returns whether we can/should start the preview or not.
|
*/
|
private boolean checkPreviewPreconditions() {
|
if (mPaused) {
|
return false;
|
}
|
|
if (mCameraDevice == null) {
|
Log.w(TAG, "startPreview: camera device not ready yet.");
|
return false;
|
}
|
|
SurfaceTexture st = mActivity.getCameraAppUI().getSurfaceTexture();
|
if (st == null) {
|
Log.w(TAG, "startPreview: surfaceTexture is not ready.");
|
return false;
|
}
|
|
if (!mCameraPreviewParamsReady) {
|
Log.w(TAG, "startPreview: parameters for preview is not ready.");
|
return false;
|
}
|
return true;
|
}
|
|
/**
|
* The start/stop preview should only run on the UI thread.
|
*/
|
private void startPreview() {
|
if (mCameraDevice == null) {
|
Log.i(TAG, "attempted to start preview before camera device");
|
// do nothing
|
return;
|
}
|
|
if (!checkPreviewPreconditions()) {
|
return;
|
}
|
|
setDisplayOrientation();
|
|
if (!mSnapshotOnIdle) {
|
// If the focus mode is continuous autofocus, call cancelAutoFocus
|
// to resume it because it may have been paused by autoFocus call.
|
if (mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()) ==
|
CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) {
|
mCameraDevice.cancelAutoFocus();
|
}
|
mFocusManager.setAeAwbLock(false); // Unlock AE and AWB.
|
}
|
|
// Nexus 4 must have picture size set to > 640x480 before other
|
// parameters are set in setCameraParameters, b/18227551. This call to
|
// updateParametersPictureSize should occur before setCameraParameters
|
// to address the issue.
|
updateParametersPictureSize();
|
|
setCameraParameters(UPDATE_PARAM_ALL);
|
|
mCameraDevice.setPreviewTexture(mActivity.getCameraAppUI().getSurfaceTexture());
|
|
Log.i(TAG, "startPreview");
|
// If we're using API2 in portability layers, don't use startPreviewWithCallback()
|
// b/17576554
|
CameraAgent.CameraStartPreviewCallback startPreviewCallback =
|
new CameraAgent.CameraStartPreviewCallback() {
|
@Override
|
public void onPreviewStarted() {
|
mFocusManager.onPreviewStarted();
|
PhotoModule.this.onPreviewStarted();
|
SessionStatsCollector.instance().previewActive(true);
|
if (mSnapshotOnIdle) {
|
mHandler.post(mDoSnapRunnable);
|
}
|
}
|
};
|
if (GservicesHelper.useCamera2ApiThroughPortabilityLayer(mActivity.getContentResolver())) {
|
mCameraDevice.startPreview();
|
startPreviewCallback.onPreviewStarted();
|
} else {
|
mCameraDevice.startPreviewWithCallback(new Handler(Looper.getMainLooper()),
|
startPreviewCallback);
|
}
|
}
|
|
@Override
|
public void stopPreview() {
|
if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
|
Log.i(TAG, "stopPreview");
|
mCameraDevice.stopPreview();
|
mFaceDetectionStarted = false;
|
}
|
setCameraState(PREVIEW_STOPPED);
|
if (mFocusManager != null) {
|
mFocusManager.onPreviewStopped();
|
}
|
SessionStatsCollector.instance().previewActive(false);
|
}
|
|
@Override
|
public void onSettingChanged(SettingsManager settingsManager, String key) {
|
if (key.equals(Keys.KEY_FLASH_MODE)) {
|
updateParametersFlashMode();
|
}
|
if (key.equals(Keys.KEY_CAMERA_HDR)) {
|
if (settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
|
Keys.KEY_CAMERA_HDR)) {
|
// HDR is on.
|
mAppController.getButtonManager().disableButton(ButtonManager.BUTTON_FLASH);
|
mFlashModeBeforeSceneMode = settingsManager.getString(
|
mAppController.getCameraScope(), Keys.KEY_FLASH_MODE);
|
} else {
|
if (mFlashModeBeforeSceneMode != null) {
|
settingsManager.set(mAppController.getCameraScope(),
|
Keys.KEY_FLASH_MODE,
|
mFlashModeBeforeSceneMode);
|
updateParametersFlashMode();
|
mFlashModeBeforeSceneMode = null;
|
}
|
mAppController.getButtonManager().enableButton(ButtonManager.BUTTON_FLASH);
|
}
|
}
|
|
if (mCameraDevice != null) {
|
mCameraDevice.applySettings(mCameraSettings);
|
}
|
}
|
|
private void updateCameraParametersInitialize() {
|
// Reset preview frame rate to the maximum because it may be lowered by
|
// video camera application.
|
int[] fpsRange = CameraUtil.getPhotoPreviewFpsRange(mCameraCapabilities);
|
if (fpsRange != null && fpsRange.length > 0) {
|
mCameraSettings.setPreviewFpsRange(fpsRange[0], fpsRange[1]);
|
}
|
|
mCameraSettings.setRecordingHintEnabled(false);
|
|
if (mCameraCapabilities.supports(CameraCapabilities.Feature.VIDEO_STABILIZATION)) {
|
mCameraSettings.setVideoStabilization(false);
|
}
|
}
|
|
private void updateCameraParametersZoom() {
|
// Set zoom.
|
if (mCameraCapabilities.supports(CameraCapabilities.Feature.ZOOM)) {
|
mCameraSettings.setZoomRatio(mZoomValue);
|
}
|
}
|
|
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
private void setAutoExposureLockIfSupported() {
|
if (mAeLockSupported) {
|
mCameraSettings.setAutoExposureLock(mFocusManager.getAeAwbLock());
|
}
|
}
|
|
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
private void setAutoWhiteBalanceLockIfSupported() {
|
if (mAwbLockSupported) {
|
mCameraSettings.setAutoWhiteBalanceLock(mFocusManager.getAeAwbLock());
|
}
|
}
|
|
private void setFocusAreasIfSupported() {
|
if (mFocusAreaSupported) {
|
mCameraSettings.setFocusAreas(mFocusManager.getFocusAreas());
|
}
|
}
|
|
private void setMeteringAreasIfSupported() {
|
if (mMeteringAreaSupported) {
|
mCameraSettings.setMeteringAreas(mFocusManager.getMeteringAreas());
|
}
|
}
|
|
private void updateCameraParametersPreference() {
|
// some monkey tests can get here when shutting the app down
|
// make sure mCameraDevice is still valid, b/17580046
|
if (mCameraDevice == null) {
|
return;
|
}
|
|
setAutoExposureLockIfSupported();
|
setAutoWhiteBalanceLockIfSupported();
|
setFocusAreasIfSupported();
|
setMeteringAreasIfSupported();
|
|
// Initialize focus mode.
|
mFocusManager.overrideFocusMode(null);
|
mCameraSettings
|
.setFocusMode(mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()));
|
SessionStatsCollector.instance().autofocusActive(
|
mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()) ==
|
CameraCapabilities.FocusMode.CONTINUOUS_PICTURE
|
);
|
|
// Set JPEG quality.
|
updateParametersPictureQuality();
|
|
// For the following settings, we need to check if the settings are
|
// still supported by latest driver, if not, ignore the settings.
|
|
// Set exposure compensation
|
updateParametersExposureCompensation();
|
|
// Set the scene mode: also sets flash and white balance.
|
updateParametersSceneMode();
|
|
if (mContinuousFocusSupported && ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK) {
|
updateAutoFocusMoveCallback();
|
}
|
}
|
|
/**
|
* This method sets picture size parameters. Size parameters should only be
|
* set when the preview is stopped, and so this method is only invoked in
|
* {@link #startPreview()} just before starting the preview.
|
*/
|
private void updateParametersPictureSize() {
|
if (mCameraDevice == null) {
|
Log.w(TAG, "attempting to set picture size without caemra device");
|
return;
|
}
|
|
List<Size> supported = Size.convert(mCameraCapabilities.getSupportedPhotoSizes());
|
CameraPictureSizesCacher.updateSizesForCamera(mAppController.getAndroidContext(),
|
mCameraDevice.getCameraId(), supported);
|
|
OneCamera.Facing cameraFacing =
|
isCameraFrontFacing() ? OneCamera.Facing.FRONT : OneCamera.Facing.BACK;
|
Size pictureSize;
|
try {
|
pictureSize = mAppController.getResolutionSetting().getPictureSize(
|
mAppController.getCameraProvider().getCurrentCameraId(),
|
cameraFacing);
|
} catch (OneCameraAccessException ex) {
|
mAppController.getFatalErrorHandler().onGenericCameraAccessFailure();
|
return;
|
}
|
|
mCameraSettings.setPhotoSize(pictureSize.toPortabilitySize());
|
|
if (ApiHelper.IS_NEXUS_5) {
|
if (ResolutionUtil.NEXUS_5_LARGE_16_BY_9.equals(pictureSize)) {
|
mShouldResizeTo16x9 = true;
|
} else {
|
mShouldResizeTo16x9 = false;
|
}
|
}
|
|
// Set a preview size that is closest to the viewfinder height and has
|
// the right aspect ratio.
|
List<Size> sizes = Size.convert(mCameraCapabilities.getSupportedPreviewSizes());
|
Size optimalSize = CameraUtil.getOptimalPreviewSize(sizes,
|
(double) pictureSize.width() / pictureSize.height());
|
Size original = new Size(mCameraSettings.getCurrentPreviewSize());
|
if (!optimalSize.equals(original)) {
|
Log.v(TAG, "setting preview size. optimal: " + optimalSize + "original: " + original);
|
mCameraSettings.setPreviewSize(optimalSize.toPortabilitySize());
|
|
mCameraDevice.applySettings(mCameraSettings);
|
mCameraSettings = mCameraDevice.getSettings();
|
}
|
|
if (optimalSize.width() != 0 && optimalSize.height() != 0) {
|
Log.v(TAG, "updating aspect ratio");
|
mUI.updatePreviewAspectRatio((float) optimalSize.width()
|
/ (float) optimalSize.height());
|
}
|
Log.d(TAG, "Preview size is " + optimalSize);
|
}
|
|
private void updateParametersPictureQuality() {
|
int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
|
CameraProfile.QUALITY_HIGH);
|
mCameraSettings.setPhotoJpegCompressionQuality(jpegQuality);
|
}
|
|
private void updateParametersExposureCompensation() {
|
SettingsManager settingsManager = mActivity.getSettingsManager();
|
if (settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
|
Keys.KEY_EXPOSURE_COMPENSATION_ENABLED)) {
|
int value = settingsManager.getInteger(mAppController.getCameraScope(),
|
Keys.KEY_EXPOSURE);
|
int max = mCameraCapabilities.getMaxExposureCompensation();
|
int min = mCameraCapabilities.getMinExposureCompensation();
|
if (value >= min && value <= max) {
|
mCameraSettings.setExposureCompensationIndex(value);
|
} else {
|
Log.w(TAG, "invalid exposure range: " + value);
|
}
|
} else {
|
// If exposure compensation is not enabled, reset the exposure compensation value.
|
setExposureCompensation(0);
|
}
|
}
|
|
private void updateParametersSceneMode() {
|
CameraCapabilities.Stringifier stringifier = mCameraCapabilities.getStringifier();
|
SettingsManager settingsManager = mActivity.getSettingsManager();
|
|
mSceneMode = stringifier.
|
sceneModeFromString(settingsManager.getString(mAppController.getCameraScope(),
|
Keys.KEY_SCENE_MODE));
|
if (mCameraCapabilities.supports(mSceneMode)) {
|
if (mCameraSettings.getCurrentSceneMode() != mSceneMode) {
|
mCameraSettings.setSceneMode(mSceneMode);
|
|
// Setting scene mode will change the settings of flash mode,
|
// white balance, and focus mode. Here we read back the
|
// parameters, so we can know those settings.
|
mCameraDevice.applySettings(mCameraSettings);
|
mCameraSettings = mCameraDevice.getSettings();
|
}
|
} else {
|
mSceneMode = mCameraSettings.getCurrentSceneMode();
|
if (mSceneMode == null) {
|
mSceneMode = CameraCapabilities.SceneMode.AUTO;
|
}
|
}
|
|
if (CameraCapabilities.SceneMode.AUTO == mSceneMode) {
|
// Set flash mode.
|
updateParametersFlashMode();
|
|
// Set focus mode.
|
mFocusManager.overrideFocusMode(null);
|
mCameraSettings.setFocusMode(
|
mFocusManager.getFocusMode(mCameraSettings.getCurrentFocusMode()));
|
} else {
|
mFocusManager.overrideFocusMode(mCameraSettings.getCurrentFocusMode());
|
}
|
}
|
|
private void updateParametersFlashMode() {
|
SettingsManager settingsManager = mActivity.getSettingsManager();
|
|
CameraCapabilities.FlashMode flashMode = mCameraCapabilities.getStringifier()
|
.flashModeFromString(settingsManager.getString(mAppController.getCameraScope(),
|
Keys.KEY_FLASH_MODE));
|
if (mCameraCapabilities.supports(flashMode)) {
|
mCameraSettings.setFlashMode(flashMode);
|
}
|
}
|
|
@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
|
private void updateAutoFocusMoveCallback() {
|
if (mCameraDevice == null) {
|
return;
|
}
|
if (mCameraSettings.getCurrentFocusMode() ==
|
CameraCapabilities.FocusMode.CONTINUOUS_PICTURE) {
|
mCameraDevice.setAutoFocusMoveCallback(mHandler,
|
(CameraAFMoveCallback) mAutoFocusMoveCallback);
|
} else {
|
mCameraDevice.setAutoFocusMoveCallback(null, null);
|
}
|
}
|
|
/**
|
* Sets the exposure compensation to the given value and also updates settings.
|
*
|
* @param value exposure compensation value to be set
|
*/
|
public void setExposureCompensation(int value) {
|
int max = mCameraCapabilities.getMaxExposureCompensation();
|
int min = mCameraCapabilities.getMinExposureCompensation();
|
if (value >= min && value <= max) {
|
mCameraSettings.setExposureCompensationIndex(value);
|
SettingsManager settingsManager = mActivity.getSettingsManager();
|
settingsManager.set(mAppController.getCameraScope(),
|
Keys.KEY_EXPOSURE, value);
|
} else {
|
Log.w(TAG, "invalid exposure range: " + value);
|
}
|
}
|
|
// We separate the parameters into several subsets, so we can update only
|
// the subsets actually need updating. The PREFERENCE set needs extra
|
// locking because the preference can be changed from GLThread as well.
|
private void setCameraParameters(int updateSet) {
|
if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {
|
updateCameraParametersInitialize();
|
}
|
|
if ((updateSet & UPDATE_PARAM_ZOOM) != 0) {
|
updateCameraParametersZoom();
|
}
|
|
if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {
|
updateCameraParametersPreference();
|
}
|
|
if (mCameraDevice != null) {
|
mCameraDevice.applySettings(mCameraSettings);
|
}
|
}
|
|
// If the Camera is idle, update the parameters immediately, otherwise
|
// accumulate them in mUpdateSet and update later.
|
private void setCameraParametersWhenIdle(int additionalUpdateSet) {
|
mUpdateSet |= additionalUpdateSet;
|
if (mCameraDevice == null) {
|
// We will update all the parameters when we open the device, so
|
// we don't need to do anything now.
|
mUpdateSet = 0;
|
return;
|
} else if (isCameraIdle()) {
|
setCameraParameters(mUpdateSet);
|
updateSceneMode();
|
mUpdateSet = 0;
|
} else {
|
if (!mHandler.hasMessages(MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE)) {
|
mHandler.sendEmptyMessageDelayed(MSG_SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);
|
}
|
}
|
}
|
|
@Override
|
public boolean isCameraIdle() {
|
return (mCameraState == IDLE) ||
|
(mCameraState == PREVIEW_STOPPED) ||
|
((mFocusManager != null) && mFocusManager.isFocusCompleted()
|
&& (mCameraState != SWITCHING_CAMERA));
|
}
|
|
@Override
|
public boolean isImageCaptureIntent() {
|
String action = mActivity.getIntent().getAction();
|
return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
|
|| CameraActivity.ACTION_IMAGE_CAPTURE_SECURE.equals(action));
|
}
|
|
private void setupCaptureParams() {
|
Bundle myExtras = mActivity.getIntent().getExtras();
|
if (myExtras != null) {
|
mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
|
mCropValue = myExtras.getString("crop");
|
}
|
}
|
|
private void initializeCapabilities() {
|
mCameraCapabilities = mCameraDevice.getCapabilities();
|
mFocusAreaSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.FOCUS_AREA);
|
mMeteringAreaSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.METERING_AREA);
|
mAeLockSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.AUTO_EXPOSURE_LOCK);
|
mAwbLockSupported = mCameraCapabilities.supports(CameraCapabilities.Feature.AUTO_WHITE_BALANCE_LOCK);
|
mContinuousFocusSupported =
|
mCameraCapabilities.supports(CameraCapabilities.FocusMode.CONTINUOUS_PICTURE);
|
}
|
|
@Override
|
public void onZoomChanged(float ratio) {
|
// Not useful to change zoom value when the activity is paused.
|
if (mPaused) {
|
return;
|
}
|
mZoomValue = ratio;
|
if (mCameraSettings == null || mCameraDevice == null) {
|
return;
|
}
|
// Set zoom parameters asynchronously
|
mCameraSettings.setZoomRatio(mZoomValue);
|
mCameraDevice.applySettings(mCameraSettings);
|
}
|
|
@Override
|
public int getCameraState() {
|
return mCameraState;
|
}
|
|
@Override
|
public void onMemoryStateChanged(int state) {
|
mAppController.setShutterEnabled(state == MemoryManager.STATE_OK);
|
}
|
|
@Override
|
public void onLowMemory() {
|
// Not much we can do in the photo module.
|
}
|
|
// For debugging only.
|
public void setDebugUri(Uri uri) {
|
mDebugUri = uri;
|
}
|
|
// For debugging only.
|
private void saveToDebugUri(byte[] data) {
|
if (mDebugUri != null) {
|
OutputStream outputStream = null;
|
try {
|
outputStream = mContentResolver.openOutputStream(mDebugUri);
|
outputStream.write(data);
|
outputStream.close();
|
} catch (IOException e) {
|
Log.e(TAG, "Exception while writing debug jpeg file", e);
|
} finally {
|
CameraUtil.closeSilently(outputStream);
|
}
|
}
|
}
|
|
@Override
|
public void onRemoteShutterPress() {
|
mHandler.post(new Runnable() {
|
@Override
|
public void run() {
|
focusAndCapture();
|
}
|
});
|
}
|
}
|