diff options
Diffstat (limited to 'src/com/android/camera/PanoramaModule.java')
-rw-r--r-- | src/com/android/camera/PanoramaModule.java | 1304 |
1 files changed, 0 insertions, 1304 deletions
diff --git a/src/com/android/camera/PanoramaModule.java b/src/com/android/camera/PanoramaModule.java deleted file mode 100644 index 007ea7a4c..000000000 --- a/src/com/android/camera/PanoramaModule.java +++ /dev/null @@ -1,1304 +0,0 @@ -/* - * Copyright (C) 2011 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.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.ImageFormat; -import android.graphics.Matrix; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.SurfaceTexture; -import android.graphics.YuvImage; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.hardware.Camera.Parameters; -import android.hardware.Camera.Size; -import android.location.Location; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Handler; -import android.os.Message; -import android.os.PowerManager; -import android.util.Log; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.OrientationEventListener; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.camera.CameraManager.CameraProxy; -import com.android.camera.ui.LayoutChangeNotifier; -import com.android.camera.ui.LayoutNotifyView; -import com.android.camera.ui.PopupManager; -import com.android.camera.ui.Rotatable; -import com.android.gallery3d.R; -import com.android.gallery3d.common.ApiHelper; -import com.android.gallery3d.exif.ExifInterface; -import com.android.gallery3d.exif.ExifTag; -import com.android.gallery3d.ui.GLRootView; -import com.android.gallery3d.util.UsageStatistics; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; -import java.util.TimeZone; - -/** - * Activity to handle panorama capturing. - */ -@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) // uses SurfaceTexture -public class PanoramaModule implements CameraModule, - SurfaceTexture.OnFrameAvailableListener, - ShutterButton.OnShutterButtonListener, - LayoutChangeNotifier.Listener { - - public static final int DEFAULT_SWEEP_ANGLE = 160; - public static final int DEFAULT_BLEND_MODE = Mosaic.BLENDTYPE_HORIZONTAL; - public static final int DEFAULT_CAPTURE_PIXELS = 960 * 720; - - private static final int MSG_LOW_RES_FINAL_MOSAIC_READY = 1; - private static final int MSG_GENERATE_FINAL_MOSAIC_ERROR = 2; - private static final int MSG_END_DIALOG_RESET_TO_PREVIEW = 3; - private static final int MSG_CLEAR_SCREEN_DELAY = 4; - private static final int MSG_CONFIG_MOSAIC_PREVIEW = 5; - private static final int MSG_RESET_TO_PREVIEW = 6; - - private static final int SCREEN_DELAY = 2 * 60 * 1000; - - private static final String TAG = "CAM PanoModule"; - private static final int PREVIEW_STOPPED = 0; - private static final int PREVIEW_ACTIVE = 1; - private static final int CAPTURE_STATE_VIEWFINDER = 0; - private static final int CAPTURE_STATE_MOSAIC = 1; - // The unit of speed is degrees per frame. - private static final float PANNING_SPEED_THRESHOLD = 2.5f; - - private ContentResolver mContentResolver; - - private GLRootView mGLRootView; - private ViewGroup mPanoLayout; - private LinearLayout mCaptureLayout; - private View mReviewLayout; - private ImageView mReview; - private View mCaptureIndicator; - private PanoProgressBar mPanoProgressBar; - private PanoProgressBar mSavingProgressBar; - private Matrix mProgressDirectionMatrix = new Matrix(); - private float[] mProgressAngle = new float[2]; - private LayoutNotifyView mPreviewArea; - private View mLeftIndicator; - private View mRightIndicator; - private MosaicPreviewRenderer mMosaicPreviewRenderer; - private Object mRendererLock = new Object(); - private TextView mTooFastPrompt; - private ShutterButton mShutterButton; - private Object mWaitObject = new Object(); - - private String mPreparePreviewString; - private String mDialogTitle; - private String mDialogOkString; - private String mDialogPanoramaFailedString; - private String mDialogWaitingPreviousString; - - private int mIndicatorColor; - private int mIndicatorColorFast; - private int mReviewBackground; - - private boolean mUsingFrontCamera; - private int mPreviewWidth; - private int mPreviewHeight; - private int mCameraState; - private int mCaptureState; - private PowerManager.WakeLock mPartialWakeLock; - private MosaicFrameProcessor mMosaicFrameProcessor; - private boolean mMosaicFrameProcessorInitialized; - private AsyncTask <Void, Void, Void> mWaitProcessorTask; - private long mTimeTaken; - private Handler mMainHandler; - private SurfaceTexture mCameraTexture; - private boolean mThreadRunning; - private boolean mCancelComputation; - private float mHorizontalViewAngle; - private float mVerticalViewAngle; - - // Prefer FOCUS_MODE_INFINITY to FOCUS_MODE_CONTINUOUS_VIDEO because of - // getting a better image quality by the former. - private String mTargetFocusMode = Parameters.FOCUS_MODE_INFINITY; - - private PanoOrientationEventListener mOrientationEventListener; - // The value could be 0, 90, 180, 270 for the 4 different orientations measured in clockwise - // respectively. - private int mDeviceOrientation; - private int mDeviceOrientationAtCapture; - private int mCameraOrientation; - private int mOrientationCompensation; - - private RotateDialogController mRotateDialog; - - private SoundClips.Player mSoundPlayer; - - private Runnable mOnFrameAvailableRunnable; - - private CameraActivity mActivity; - private View mRootView; - private CameraProxy mCameraDevice; - private boolean mPaused; - private boolean mIsCreatingRenderer; - - private LocationManager mLocationManager; - private ComboPreferences mPreferences; - - private class MosaicJpeg { - public MosaicJpeg(byte[] data, int width, int height) { - this.data = data; - this.width = width; - this.height = height; - this.isValid = true; - } - - public MosaicJpeg() { - this.data = null; - this.width = 0; - this.height = 0; - this.isValid = false; - } - - public final byte[] data; - public final int width; - public final int height; - public final boolean isValid; - } - - private class PanoOrientationEventListener extends OrientationEventListener { - public PanoOrientationEventListener(Context context) { - super(context); - } - - @Override - public void onOrientationChanged(int orientation) { - // We keep the last known orientation. So if the user first orient - // the camera then point the camera to floor or sky, we still have - // the correct orientation. - if (orientation == ORIENTATION_UNKNOWN) return; - mDeviceOrientation = Util.roundOrientation(orientation, mDeviceOrientation); - // When the screen is unlocked, display rotation may change. Always - // calculate the up-to-date orientationCompensation. - int orientationCompensation = mDeviceOrientation - + Util.getDisplayRotation(mActivity) % 360; - if (mOrientationCompensation != orientationCompensation) { - mOrientationCompensation = orientationCompensation; - mActivity.getGLRoot().requestLayoutContentPane(); - } - } - } - - @Override - public void init(CameraActivity activity, View parent, boolean reuseScreenNail) { - mActivity = activity; - mRootView = parent; - - createContentView(); - - mContentResolver = mActivity.getContentResolver(); - if (reuseScreenNail) { - mActivity.reuseCameraScreenNail(true); - } else { - mActivity.createCameraScreenNail(true); - } - - // This runs in UI thread. - mOnFrameAvailableRunnable = new Runnable() { - @Override - public void run() { - // Frames might still be available after the activity is paused. - // If we call onFrameAvailable after pausing, the GL thread will crash. - if (mPaused) return; - - MosaicPreviewRenderer renderer = null; - synchronized (mRendererLock) { - if (mMosaicPreviewRenderer == null) { - return; - } - renderer = mMosaicPreviewRenderer; - } - if (mGLRootView.getVisibility() != View.VISIBLE) { - renderer.showPreviewFrameSync(); - mGLRootView.setVisibility(View.VISIBLE); - } else { - if (mCaptureState == CAPTURE_STATE_VIEWFINDER) { - renderer.showPreviewFrame(); - } else { - renderer.alignFrameSync(); - mMosaicFrameProcessor.processFrame(); - } - } - } - }; - - PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE); - mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Panorama"); - - mOrientationEventListener = new PanoOrientationEventListener(mActivity); - - mMosaicFrameProcessor = MosaicFrameProcessor.getInstance(); - - Resources appRes = mActivity.getResources(); - mPreparePreviewString = appRes.getString(R.string.pano_dialog_prepare_preview); - mDialogTitle = appRes.getString(R.string.pano_dialog_title); - mDialogOkString = appRes.getString(R.string.dialog_ok); - mDialogPanoramaFailedString = appRes.getString(R.string.pano_dialog_panorama_failed); - mDialogWaitingPreviousString = appRes.getString(R.string.pano_dialog_waiting_previous); - - mGLRootView = (GLRootView) mActivity.getGLRoot(); - - mPreferences = new ComboPreferences(mActivity); - CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal()); - mLocationManager = new LocationManager(mActivity, null); - - mMainHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_LOW_RES_FINAL_MOSAIC_READY: - onBackgroundThreadFinished(); - showFinalMosaic((Bitmap) msg.obj); - saveHighResMosaic(); - break; - case MSG_GENERATE_FINAL_MOSAIC_ERROR: - onBackgroundThreadFinished(); - if (mPaused) { - resetToPreview(); - } else { - mRotateDialog.showAlertDialog( - mDialogTitle, mDialogPanoramaFailedString, - mDialogOkString, new Runnable() { - @Override - public void run() { - resetToPreview(); - }}, - null, null); - } - clearMosaicFrameProcessorIfNeeded(); - break; - case MSG_END_DIALOG_RESET_TO_PREVIEW: - onBackgroundThreadFinished(); - resetToPreview(); - clearMosaicFrameProcessorIfNeeded(); - break; - case MSG_CLEAR_SCREEN_DELAY: - mActivity.getWindow().clearFlags(WindowManager.LayoutParams. - FLAG_KEEP_SCREEN_ON); - break; - case MSG_CONFIG_MOSAIC_PREVIEW: - configMosaicPreview(msg.arg1, msg.arg2); - break; - case MSG_RESET_TO_PREVIEW: - resetToPreview(); - break; - } - } - }; - } - - @Override - public boolean dispatchTouchEvent(MotionEvent m) { - return mActivity.superDispatchTouchEvent(m); - } - - private void setupCamera() throws CameraHardwareException, CameraDisabledException { - openCamera(); - Parameters parameters = mCameraDevice.getParameters(); - setupCaptureParams(parameters); - configureCamera(parameters); - } - - private void releaseCamera() { - if (mCameraDevice != null) { - mCameraDevice.setPreviewCallbackWithBuffer(null); - CameraHolder.instance().release(); - mCameraDevice = null; - mCameraState = PREVIEW_STOPPED; - } - } - - private void openCamera() throws CameraHardwareException, CameraDisabledException { - int cameraId = CameraHolder.instance().getBackCameraId(); - // If there is no back camera, use the first camera. Camera id starts - // from 0. Currently if a camera is not back facing, it is front facing. - // This is also forward compatible if we have a new facing other than - // back or front in the future. - if (cameraId == -1) cameraId = 0; - mCameraDevice = Util.openCamera(mActivity, cameraId); - mCameraOrientation = Util.getCameraOrientation(cameraId); - if (cameraId == CameraHolder.instance().getFrontCameraId()) mUsingFrontCamera = true; - } - - private boolean findBestPreviewSize(List<Size> supportedSizes, boolean need4To3, - boolean needSmaller) { - int pixelsDiff = DEFAULT_CAPTURE_PIXELS; - boolean hasFound = false; - for (Size size : supportedSizes) { - int h = size.height; - int w = size.width; - // we only want 4:3 format. - int d = DEFAULT_CAPTURE_PIXELS - h * w; - if (needSmaller && d < 0) { // no bigger preview than 960x720. - continue; - } - if (need4To3 && (h * 4 != w * 3)) { - continue; - } - d = Math.abs(d); - if (d < pixelsDiff) { - mPreviewWidth = w; - mPreviewHeight = h; - pixelsDiff = d; - hasFound = true; - } - } - return hasFound; - } - - private void setupCaptureParams(Parameters parameters) { - List<Size> supportedSizes = parameters.getSupportedPreviewSizes(); - if (!findBestPreviewSize(supportedSizes, true, true)) { - Log.w(TAG, "No 4:3 ratio preview size supported."); - if (!findBestPreviewSize(supportedSizes, false, true)) { - Log.w(TAG, "Can't find a supported preview size smaller than 960x720."); - findBestPreviewSize(supportedSizes, false, false); - } - } - Log.v(TAG, "preview h = " + mPreviewHeight + " , w = " + mPreviewWidth); - parameters.setPreviewSize(mPreviewWidth, mPreviewHeight); - - List<int[]> frameRates = parameters.getSupportedPreviewFpsRange(); - int last = frameRates.size() - 1; - int minFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MIN_INDEX]; - int maxFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MAX_INDEX]; - parameters.setPreviewFpsRange(minFps, maxFps); - Log.v(TAG, "preview fps: " + minFps + ", " + maxFps); - - List<String> supportedFocusModes = parameters.getSupportedFocusModes(); - if (supportedFocusModes.indexOf(mTargetFocusMode) >= 0) { - parameters.setFocusMode(mTargetFocusMode); - } else { - // Use the default focus mode and log a message - Log.w(TAG, "Cannot set the focus mode to " + mTargetFocusMode + - " becuase the mode is not supported."); - } - - parameters.set(Util.RECORDING_HINT, Util.FALSE); - - mHorizontalViewAngle = parameters.getHorizontalViewAngle(); - mVerticalViewAngle = parameters.getVerticalViewAngle(); - } - - public int getPreviewBufSize() { - PixelFormat pixelInfo = new PixelFormat(); - PixelFormat.getPixelFormatInfo(mCameraDevice.getParameters().getPreviewFormat(), pixelInfo); - // TODO: remove this extra 32 byte after the driver bug is fixed. - return (mPreviewWidth * mPreviewHeight * pixelInfo.bitsPerPixel / 8) + 32; - } - - private void configureCamera(Parameters parameters) { - mCameraDevice.setParameters(parameters); - } - - private void configMosaicPreview(final int w, final int h) { - synchronized (mRendererLock) { - if (mIsCreatingRenderer) { - mMainHandler.removeMessages(MSG_CONFIG_MOSAIC_PREVIEW); - mMainHandler.obtainMessage(MSG_CONFIG_MOSAIC_PREVIEW, w, h).sendToTarget(); - return; - } - mIsCreatingRenderer = true; - } - stopCameraPreview(); - CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail; - screenNail.setSize(w, h); - synchronized (mRendererLock) { - if (mMosaicPreviewRenderer != null) { - mMosaicPreviewRenderer.release(); - } - mMosaicPreviewRenderer = null; - screenNail.releaseSurfaceTexture(); - screenNail.acquireSurfaceTexture(); - } - mActivity.notifyScreenNailChanged(); - final boolean isLandscape = (mActivity.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE); - new Thread(new Runnable() { - @Override - public void run() { - CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail; - SurfaceTexture surfaceTexture = screenNail.getSurfaceTexture(); - if (surfaceTexture == null) { - synchronized (mRendererLock) { - mIsCreatingRenderer = false; - mRendererLock.notifyAll(); - return; - } - } - MosaicPreviewRenderer renderer = new MosaicPreviewRenderer( - screenNail.getSurfaceTexture(), w, h, isLandscape); - synchronized (mRendererLock) { - mMosaicPreviewRenderer = renderer; - mCameraTexture = mMosaicPreviewRenderer.getInputSurfaceTexture(); - - if (!mPaused && !mThreadRunning && mWaitProcessorTask == null) { - mMainHandler.sendEmptyMessage(MSG_RESET_TO_PREVIEW); - } - mIsCreatingRenderer = false; - mRendererLock.notifyAll(); - } - } - }).start(); - } - - // Receives the layout change event from the preview area. So we can set - // the camera preview screennail to the same size and initialize the mosaic - // preview renderer. - @Override - public void onLayoutChange(View v, int l, int t, int r, int b) { - Log.i(TAG, "layout change: "+(r - l) + "/" +(b - t)); - mActivity.onLayoutChange(v, l, t, r, b); - configMosaicPreview(r - l, b - t); - } - - @Override - public void onFrameAvailable(SurfaceTexture surface) { - /* This function may be called by some random thread, - * so let's be safe and jump back to ui thread. - * No OpenGL calls can be done here. */ - mActivity.runOnUiThread(mOnFrameAvailableRunnable); - } - - private void hideDirectionIndicators() { - mLeftIndicator.setVisibility(View.GONE); - mRightIndicator.setVisibility(View.GONE); - } - - private void showDirectionIndicators(int direction) { - switch (direction) { - case PanoProgressBar.DIRECTION_NONE: - mLeftIndicator.setVisibility(View.VISIBLE); - mRightIndicator.setVisibility(View.VISIBLE); - break; - case PanoProgressBar.DIRECTION_LEFT: - mLeftIndicator.setVisibility(View.VISIBLE); - mRightIndicator.setVisibility(View.GONE); - break; - case PanoProgressBar.DIRECTION_RIGHT: - mLeftIndicator.setVisibility(View.GONE); - mRightIndicator.setVisibility(View.VISIBLE); - break; - } - } - - public void startCapture() { - // Reset values so we can do this again. - mCancelComputation = false; - mTimeTaken = System.currentTimeMillis(); - mActivity.setSwipingEnabled(false); - mActivity.hideSwitcher(); - mShutterButton.setImageResource(R.drawable.btn_shutter_recording); - mCaptureState = CAPTURE_STATE_MOSAIC; - mCaptureIndicator.setVisibility(View.VISIBLE); - showDirectionIndicators(PanoProgressBar.DIRECTION_NONE); - - mMosaicFrameProcessor.setProgressListener(new MosaicFrameProcessor.ProgressListener() { - @Override - public void onProgress(boolean isFinished, float panningRateX, float panningRateY, - float progressX, float progressY) { - float accumulatedHorizontalAngle = progressX * mHorizontalViewAngle; - float accumulatedVerticalAngle = progressY * mVerticalViewAngle; - if (isFinished - || (Math.abs(accumulatedHorizontalAngle) >= DEFAULT_SWEEP_ANGLE) - || (Math.abs(accumulatedVerticalAngle) >= DEFAULT_SWEEP_ANGLE)) { - stopCapture(false); - } else { - float panningRateXInDegree = panningRateX * mHorizontalViewAngle; - float panningRateYInDegree = panningRateY * mVerticalViewAngle; - updateProgress(panningRateXInDegree, panningRateYInDegree, - accumulatedHorizontalAngle, accumulatedVerticalAngle); - } - } - }); - - mPanoProgressBar.reset(); - // TODO: calculate the indicator width according to different devices to reflect the actual - // angle of view of the camera device. - mPanoProgressBar.setIndicatorWidth(20); - mPanoProgressBar.setMaxProgress(DEFAULT_SWEEP_ANGLE); - mPanoProgressBar.setVisibility(View.VISIBLE); - mDeviceOrientationAtCapture = mDeviceOrientation; - keepScreenOn(); - mActivity.getOrientationManager().lockOrientation(); - setupProgressDirectionMatrix(); - } - - void setupProgressDirectionMatrix() { - int degrees = Util.getDisplayRotation(mActivity); - int cameraId = CameraHolder.instance().getBackCameraId(); - int orientation = Util.getDisplayOrientation(degrees, cameraId); - mProgressDirectionMatrix.reset(); - mProgressDirectionMatrix.postRotate(orientation); - } - - private void stopCapture(boolean aborted) { - mCaptureState = CAPTURE_STATE_VIEWFINDER; - mCaptureIndicator.setVisibility(View.GONE); - hideTooFastIndication(); - hideDirectionIndicators(); - - mMosaicFrameProcessor.setProgressListener(null); - stopCameraPreview(); - - mCameraTexture.setOnFrameAvailableListener(null); - - if (!aborted && !mThreadRunning) { - mRotateDialog.showWaitingDialog(mPreparePreviewString); - // Hide shutter button, shutter icon, etc when waiting for - // panorama to stitch - mActivity.hideUI(); - runBackgroundThread(new Thread() { - @Override - public void run() { - MosaicJpeg jpeg = generateFinalMosaic(false); - - if (jpeg != null && jpeg.isValid) { - Bitmap bitmap = null; - bitmap = BitmapFactory.decodeByteArray(jpeg.data, 0, jpeg.data.length); - mMainHandler.sendMessage(mMainHandler.obtainMessage( - MSG_LOW_RES_FINAL_MOSAIC_READY, bitmap)); - } else { - mMainHandler.sendMessage(mMainHandler.obtainMessage( - MSG_END_DIALOG_RESET_TO_PREVIEW)); - } - } - }); - } - keepScreenOnAwhile(); - } - - private void showTooFastIndication() { - mTooFastPrompt.setVisibility(View.VISIBLE); - // The PreviewArea also contains the border for "too fast" indication. - mPreviewArea.setVisibility(View.VISIBLE); - mPanoProgressBar.setIndicatorColor(mIndicatorColorFast); - mLeftIndicator.setEnabled(true); - mRightIndicator.setEnabled(true); - } - - private void hideTooFastIndication() { - mTooFastPrompt.setVisibility(View.GONE); - // We set "INVISIBLE" instead of "GONE" here because we need mPreviewArea to have layout - // information so we can know the size and position for mCameraScreenNail. - mPreviewArea.setVisibility(View.INVISIBLE); - mPanoProgressBar.setIndicatorColor(mIndicatorColor); - mLeftIndicator.setEnabled(false); - mRightIndicator.setEnabled(false); - } - - private void updateProgress(float panningRateXInDegree, float panningRateYInDegree, - float progressHorizontalAngle, float progressVerticalAngle) { - mGLRootView.requestRender(); - - if ((Math.abs(panningRateXInDegree) > PANNING_SPEED_THRESHOLD) - || (Math.abs(panningRateYInDegree) > PANNING_SPEED_THRESHOLD)) { - showTooFastIndication(); - } else { - hideTooFastIndication(); - } - - // progressHorizontalAngle and progressVerticalAngle are relative to the - // camera. Convert them to UI direction. - mProgressAngle[0] = progressHorizontalAngle; - mProgressAngle[1] = progressVerticalAngle; - mProgressDirectionMatrix.mapPoints(mProgressAngle); - - int angleInMajorDirection = - (Math.abs(mProgressAngle[0]) > Math.abs(mProgressAngle[1])) - ? (int) mProgressAngle[0] - : (int) mProgressAngle[1]; - mPanoProgressBar.setProgress((angleInMajorDirection)); - } - - private void setViews(Resources appRes) { - mCaptureState = CAPTURE_STATE_VIEWFINDER; - mPanoProgressBar = (PanoProgressBar) mRootView.findViewById(R.id.pano_pan_progress_bar); - mPanoProgressBar.setBackgroundColor(appRes.getColor(R.color.pano_progress_empty)); - mPanoProgressBar.setDoneColor(appRes.getColor(R.color.pano_progress_done)); - mPanoProgressBar.setIndicatorColor(mIndicatorColor); - mPanoProgressBar.setOnDirectionChangeListener( - new PanoProgressBar.OnDirectionChangeListener () { - @Override - public void onDirectionChange(int direction) { - if (mCaptureState == CAPTURE_STATE_MOSAIC) { - showDirectionIndicators(direction); - } - } - }); - - mLeftIndicator = mRootView.findViewById(R.id.pano_pan_left_indicator); - mRightIndicator = mRootView.findViewById(R.id.pano_pan_right_indicator); - mLeftIndicator.setEnabled(false); - mRightIndicator.setEnabled(false); - mTooFastPrompt = (TextView) mRootView.findViewById(R.id.pano_capture_too_fast_textview); - // This mPreviewArea also shows the border for visual "too fast" indication. - mPreviewArea = (LayoutNotifyView) mRootView.findViewById(R.id.pano_preview_area); - mPreviewArea.setOnLayoutChangeListener(this); - - mSavingProgressBar = (PanoProgressBar) mRootView.findViewById(R.id.pano_saving_progress_bar); - mSavingProgressBar.setIndicatorWidth(0); - mSavingProgressBar.setMaxProgress(100); - mSavingProgressBar.setBackgroundColor(appRes.getColor(R.color.pano_progress_empty)); - mSavingProgressBar.setDoneColor(appRes.getColor(R.color.pano_progress_indication)); - - mCaptureIndicator = mRootView.findViewById(R.id.pano_capture_indicator); - - mReviewLayout = mRootView.findViewById(R.id.pano_review_layout); - mReview = (ImageView) mRootView.findViewById(R.id.pano_reviewarea); - mReview.setBackgroundColor(mReviewBackground); - View cancelButton = mRootView.findViewById(R.id.pano_review_cancel_button); - cancelButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View arg0) { - if (mPaused || mCameraTexture == null) return; - cancelHighResComputation(); - } - }); - - mShutterButton = mActivity.getShutterButton(); - mShutterButton.setImageResource(R.drawable.btn_new_shutter); - mShutterButton.setOnShutterButtonListener(this); - - if (mActivity.getResources().getConfiguration().orientation - == Configuration.ORIENTATION_PORTRAIT) { - Rotatable view = (Rotatable) mRootView.findViewById(R.id.pano_rotate_reviewarea); - view.setOrientation(270, false); - } - } - - private void createContentView() { - mActivity.getLayoutInflater().inflate(R.layout.panorama_module, (ViewGroup) mRootView, true); - Resources appRes = mActivity.getResources(); - mCaptureLayout = (LinearLayout) mRootView.findViewById(R.id.camera_app); - mIndicatorColor = appRes.getColor(R.color.pano_progress_indication); - mReviewBackground = appRes.getColor(R.color.review_background); - mIndicatorColorFast = appRes.getColor(R.color.pano_progress_indication_fast); - mPanoLayout = (ViewGroup) mRootView.findViewById(R.id.camera_app_root); - mRotateDialog = new RotateDialogController(mActivity, R.layout.rotate_dialog); - setViews(appRes); - } - - @Override - public void onShutterButtonClick() { - // If mCameraTexture == null then GL setup is not finished yet. - // No buttons can be pressed. - if (mPaused || mThreadRunning || mCameraTexture == null) return; - // Since this button will stay on the screen when capturing, we need to check the state - // right now. - switch (mCaptureState) { - case CAPTURE_STATE_VIEWFINDER: - if(mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) return; - mSoundPlayer.play(SoundClips.START_VIDEO_RECORDING); - startCapture(); - break; - case CAPTURE_STATE_MOSAIC: - mSoundPlayer.play(SoundClips.STOP_VIDEO_RECORDING); - stopCapture(false); - } - } - - @Override - public void onShutterButtonFocus(boolean pressed) { - } - - public void reportProgress() { - mSavingProgressBar.reset(); - mSavingProgressBar.setRightIncreasing(true); - Thread t = new Thread() { - @Override - public void run() { - while (mThreadRunning) { - final int progress = mMosaicFrameProcessor.reportProgress( - true, mCancelComputation); - - try { - synchronized (mWaitObject) { - mWaitObject.wait(50); - } - } catch (InterruptedException e) { - throw new RuntimeException("Panorama reportProgress failed", e); - } - // Update the progress bar - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - mSavingProgressBar.setProgress(progress); - } - }); - } - } - }; - t.start(); - } - - private int getCaptureOrientation() { - // The panorama image returned from the library is oriented based on the - // natural orientation of a camera. We need to set an orientation for the image - // in its EXIF header, so the image can be displayed correctly. - // The orientation is calculated from compensating the - // device orientation at capture and the camera orientation respective to - // the natural orientation of the device. - int orientation; - if (mUsingFrontCamera) { - // mCameraOrientation is negative with respect to the front facing camera. - // See document of android.hardware.Camera.Parameters.setRotation. - orientation = (mDeviceOrientationAtCapture - mCameraOrientation + 360) % 360; - } else { - orientation = (mDeviceOrientationAtCapture + mCameraOrientation) % 360; - } - return orientation; - } - - public void saveHighResMosaic() { - runBackgroundThread(new Thread() { - @Override - public void run() { - mPartialWakeLock.acquire(); - MosaicJpeg jpeg; - try { - jpeg = generateFinalMosaic(true); - } finally { - mPartialWakeLock.release(); - } - - if (jpeg == null) { // Cancelled by user. - mMainHandler.sendEmptyMessage(MSG_END_DIALOG_RESET_TO_PREVIEW); - } else if (!jpeg.isValid) { // Error when generating mosaic. - mMainHandler.sendEmptyMessage(MSG_GENERATE_FINAL_MOSAIC_ERROR); - } else { - int orientation = getCaptureOrientation(); - Uri uri = savePanorama(jpeg.data, jpeg.width, jpeg.height, orientation); - if (uri != null) { - mActivity.addSecureAlbumItemIfNeeded(false, uri); - Util.broadcastNewPicture(mActivity, uri); - } - mMainHandler.sendMessage( - mMainHandler.obtainMessage(MSG_END_DIALOG_RESET_TO_PREVIEW)); - } - } - }); - reportProgress(); - } - - private void runBackgroundThread(Thread thread) { - mThreadRunning = true; - thread.start(); - } - - private void onBackgroundThreadFinished() { - mThreadRunning = false; - mRotateDialog.dismissDialog(); - } - - private void cancelHighResComputation() { - mCancelComputation = true; - synchronized (mWaitObject) { - mWaitObject.notify(); - } - } - - // This function will be called upon the first camera frame is available. - private void reset() { - mCaptureState = CAPTURE_STATE_VIEWFINDER; - - mActivity.getOrientationManager().unlockOrientation(); - // We should set mGLRootView visible too. However, since there might be no - // frame available yet, setting mGLRootView visible should be done right after - // the first camera frame is available and therefore it is done by - // mOnFirstFrameAvailableRunnable. - mActivity.setSwipingEnabled(true); - mShutterButton.setImageResource(R.drawable.btn_new_shutter); - mReviewLayout.setVisibility(View.GONE); - mPanoProgressBar.setVisibility(View.GONE); - mGLRootView.setVisibility(View.VISIBLE); - // Orientation change will trigger onLayoutChange->configMosaicPreview-> - // resetToPreview. Do not show the capture UI in film strip. - if (mActivity.mShowCameraAppView) { - mCaptureLayout.setVisibility(View.VISIBLE); - mActivity.showUI(); - } - mMosaicFrameProcessor.reset(); - } - - private void resetToPreview() { - reset(); - if (!mPaused) startCameraPreview(); - } - - private static class FlipBitmapDrawable extends BitmapDrawable { - - public FlipBitmapDrawable(Resources res, Bitmap bitmap) { - super(res, bitmap); - } - - @Override - public void draw(Canvas canvas) { - Rect bounds = getBounds(); - int cx = bounds.centerX(); - int cy = bounds.centerY(); - canvas.save(Canvas.MATRIX_SAVE_FLAG); - canvas.rotate(180, cx, cy); - super.draw(canvas); - canvas.restore(); - } - } - - private void showFinalMosaic(Bitmap bitmap) { - if (bitmap != null) { - int orientation = getCaptureOrientation(); - if (orientation >= 180) { - // We need to flip the drawable to compensate - mReview.setImageDrawable(new FlipBitmapDrawable( - mActivity.getResources(), bitmap)); - } else { - mReview.setImageBitmap(bitmap); - } - } - - mCaptureLayout.setVisibility(View.GONE); - mReviewLayout.setVisibility(View.VISIBLE); - } - - private Uri savePanorama(byte[] jpegData, int width, int height, int orientation) { - if (jpegData != null) { - String filename = PanoUtil.createName( - mActivity.getResources().getString(R.string.pano_file_name_format), mTimeTaken); - String filepath = Storage.generateFilepath(filename); - - Location loc = mLocationManager.getCurrentLocation(); - ExifInterface exif = new ExifInterface(); - try { - exif.readExif(jpegData); - exif.addGpsDateTimeStampTag(mTimeTaken); - exif.addDateTimeStampTag(ExifInterface.TAG_DATE_TIME, mTimeTaken, - TimeZone.getDefault()); - exif.setTag(exif.buildTag(ExifInterface.TAG_ORIENTATION, - ExifInterface.getOrientationValueForRotation(orientation))); - writeLocation(loc, exif); - exif.writeExif(jpegData, filepath); - } catch (IOException e) { - Log.e(TAG, "Cannot set exif for " + filepath, e); - Storage.writeFile(filepath, jpegData); - } - int jpegLength = (int) (new File(filepath).length()); - return Storage.addImage(mContentResolver, filename, mTimeTaken, - loc, orientation, jpegLength, filepath, width, height); - } - return null; - } - - private static void writeLocation(Location location, ExifInterface exif) { - if (location == null) { - return; - } - exif.addGpsTags(location.getLatitude(), location.getLongitude()); - exif.setTag(exif.buildTag(ExifInterface.TAG_GPS_PROCESSING_METHOD, location.getProvider())); - } - - private void clearMosaicFrameProcessorIfNeeded() { - if (!mPaused || mThreadRunning) return; - // Only clear the processor if it is initialized by this activity - // instance. Other activity instances may be using it. - if (mMosaicFrameProcessorInitialized) { - mMosaicFrameProcessor.clear(); - mMosaicFrameProcessorInitialized = false; - } - } - - private void initMosaicFrameProcessorIfNeeded() { - if (mPaused || mThreadRunning) return; - mMosaicFrameProcessor.initialize( - mPreviewWidth, mPreviewHeight, getPreviewBufSize()); - mMosaicFrameProcessorInitialized = true; - } - - @Override - public void onPauseBeforeSuper() { - mPaused = true; - if (mLocationManager != null) mLocationManager.recordLocation(false); - } - - @Override - public void onPauseAfterSuper() { - mOrientationEventListener.disable(); - if (mCameraDevice == null) { - // Camera open failed. Nothing should be done here. - return; - } - // Stop the capturing first. - if (mCaptureState == CAPTURE_STATE_MOSAIC) { - stopCapture(true); - reset(); - } - - releaseCamera(); - synchronized (mRendererLock) { - mCameraTexture = null; - - // The preview renderer might not have a chance to be initialized - // before onPause(). - if (mMosaicPreviewRenderer != null) { - mMosaicPreviewRenderer.release(); - mMosaicPreviewRenderer = null; - } - } - - clearMosaicFrameProcessorIfNeeded(); - if (mWaitProcessorTask != null) { - mWaitProcessorTask.cancel(true); - mWaitProcessorTask = null; - } - resetScreenOn(); - if (mSoundPlayer != null) { - mSoundPlayer.release(); - mSoundPlayer = null; - } - CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail; - screenNail.releaseSurfaceTexture(); - System.gc(); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - Drawable lowResReview = null; - if (mThreadRunning) lowResReview = mReview.getDrawable(); - - // Change layout in response to configuration change - mCaptureLayout.setOrientation( - newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE - ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL); - mCaptureLayout.removeAllViews(); - LayoutInflater inflater = mActivity.getLayoutInflater(); - inflater.inflate(R.layout.preview_frame_pano, mCaptureLayout); - - mPanoLayout.removeView(mReviewLayout); - inflater.inflate(R.layout.pano_review, mPanoLayout); - - setViews(mActivity.getResources()); - if (mThreadRunning) { - mReview.setImageDrawable(lowResReview); - mCaptureLayout.setVisibility(View.GONE); - mReviewLayout.setVisibility(View.VISIBLE); - } - } - - @Override - public void onOrientationChanged(int orientation) { - } - - @Override - public void onResumeBeforeSuper() { - mPaused = false; - } - - @Override - public void onResumeAfterSuper() { - mOrientationEventListener.enable(); - - mCaptureState = CAPTURE_STATE_VIEWFINDER; - - try { - setupCamera(); - } catch (CameraHardwareException e) { - Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera); - return; - } catch (CameraDisabledException e) { - Util.showErrorAndFinish(mActivity, R.string.camera_disabled); - return; - } - - // Set up sound playback for shutter button - mSoundPlayer = SoundClips.getPlayer(mActivity); - - // Check if another panorama instance is using the mosaic frame processor. - mRotateDialog.dismissDialog(); - if (!mThreadRunning && mMosaicFrameProcessor.isMosaicMemoryAllocated()) { - mGLRootView.setVisibility(View.GONE); - mRotateDialog.showWaitingDialog(mDialogWaitingPreviousString); - // If stitching is still going on, make sure switcher and shutter button - // are not showing - mActivity.hideUI(); - mWaitProcessorTask = new WaitProcessorTask().execute(); - } else { - mGLRootView.setVisibility(View.VISIBLE); - // Camera must be initialized before MosaicFrameProcessor is - // initialized. The preview size has to be decided by camera device. - initMosaicFrameProcessorIfNeeded(); - int w = mPreviewArea.getWidth(); - int h = mPreviewArea.getHeight(); - if (w != 0 && h != 0) { // The layout has been calculated. - configMosaicPreview(w, h); - } - } - keepScreenOnAwhile(); - - // Initialize location service. - boolean recordLocation = RecordLocationPreference.get(mPreferences, - mContentResolver); - mLocationManager.recordLocation(recordLocation); - - // Dismiss open menu if exists. - PopupManager.getInstance(mActivity).notifyShowPopup(null); - mRootView.requestLayout(); - UsageStatistics.onContentViewChanged( - UsageStatistics.COMPONENT_CAMERA, "PanoramaModule"); - } - - /** - * Generate the final mosaic image. - * - * @param highRes flag to indicate whether we want to get a high-res version. - * @return a MosaicJpeg with its isValid flag set to true if successful; null if the generation - * process is cancelled; and a MosaicJpeg with its isValid flag set to false if there - * is an error in generating the final mosaic. - */ - public MosaicJpeg generateFinalMosaic(boolean highRes) { - int mosaicReturnCode = mMosaicFrameProcessor.createMosaic(highRes); - if (mosaicReturnCode == Mosaic.MOSAIC_RET_CANCELLED) { - return null; - } else if (mosaicReturnCode == Mosaic.MOSAIC_RET_ERROR) { - return new MosaicJpeg(); - } - - byte[] imageData = mMosaicFrameProcessor.getFinalMosaicNV21(); - if (imageData == null) { - Log.e(TAG, "getFinalMosaicNV21() returned null."); - return new MosaicJpeg(); - } - - int len = imageData.length - 8; - int width = (imageData[len + 0] << 24) + ((imageData[len + 1] & 0xFF) << 16) - + ((imageData[len + 2] & 0xFF) << 8) + (imageData[len + 3] & 0xFF); - int height = (imageData[len + 4] << 24) + ((imageData[len + 5] & 0xFF) << 16) - + ((imageData[len + 6] & 0xFF) << 8) + (imageData[len + 7] & 0xFF); - Log.v(TAG, "ImLength = " + (len) + ", W = " + width + ", H = " + height); - - if (width <= 0 || height <= 0) { - // TODO: pop up an error message indicating that the final result is not generated. - Log.e(TAG, "width|height <= 0!!, len = " + (len) + ", W = " + width + ", H = " + - height); - return new MosaicJpeg(); - } - - YuvImage yuvimage = new YuvImage(imageData, ImageFormat.NV21, width, height, null); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - yuvimage.compressToJpeg(new Rect(0, 0, width, height), 100, out); - try { - out.close(); - } catch (Exception e) { - Log.e(TAG, "Exception in storing final mosaic", e); - return new MosaicJpeg(); - } - return new MosaicJpeg(out.toByteArray(), width, height); - } - - private void startCameraPreview() { - if (mCameraDevice == null) { - // Camera open failed. Return. - return; - } - - // This works around a driver issue. startPreview may fail if - // stopPreview/setPreviewTexture/startPreview are called several times - // in a row. mCameraTexture can be null after pressing home during - // mosaic generation and coming back. Preview will be started later in - // onLayoutChange->configMosaicPreview. This also reduces the latency. - synchronized (mRendererLock) { - if (mCameraTexture == null) return; - - // If we're previewing already, stop the preview first (this will - // blank the screen). - if (mCameraState != PREVIEW_STOPPED) stopCameraPreview(); - - // Set the display orientation to 0, so that the underlying mosaic - // library can always get undistorted mPreviewWidth x mPreviewHeight - // image data from SurfaceTexture. - mCameraDevice.setDisplayOrientation(0); - - mCameraTexture.setOnFrameAvailableListener(this); - mCameraDevice.setPreviewTextureAsync(mCameraTexture); - } - mCameraDevice.startPreviewAsync(); - mCameraState = PREVIEW_ACTIVE; - } - - private void stopCameraPreview() { - if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) { - Log.v(TAG, "stopPreview"); - mCameraDevice.stopPreview(); - } - mCameraState = PREVIEW_STOPPED; - } - - @Override - public void onUserInteraction() { - if (mCaptureState != CAPTURE_STATE_MOSAIC) keepScreenOnAwhile(); - } - - @Override - public boolean onBackPressed() { - // If panorama is generating low res or high res mosaic, ignore back - // key. So the activity will not be destroyed. - if (mThreadRunning) return true; - return false; - } - - private void resetScreenOn() { - mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY); - mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } - - private void keepScreenOnAwhile() { - mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY); - mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_DELAY, SCREEN_DELAY); - } - - private void keepScreenOn() { - mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY); - mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } - - private class WaitProcessorTask extends AsyncTask<Void, Void, Void> { - @Override - protected Void doInBackground(Void... params) { - synchronized (mMosaicFrameProcessor) { - while (!isCancelled() && mMosaicFrameProcessor.isMosaicMemoryAllocated()) { - try { - mMosaicFrameProcessor.wait(); - } catch (Exception e) { - // ignore - } - } - } - return null; - } - - @Override - protected void onPostExecute(Void result) { - mWaitProcessorTask = null; - mRotateDialog.dismissDialog(); - mGLRootView.setVisibility(View.VISIBLE); - initMosaicFrameProcessorIfNeeded(); - int w = mPreviewArea.getWidth(); - int h = mPreviewArea.getHeight(); - if (w != 0 && h != 0) { // The layout has been calculated. - configMosaicPreview(w, h); - } - resetToPreview(); - } - } - - @Override - public void onFullScreenChanged(boolean full) { - } - - - @Override - public void onStop() { - } - - @Override - public void installIntentFilter() { - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - } - - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - return false; - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - return false; - } - - @Override - public void onSingleTapUp(View view, int x, int y) { - } - - @Override - public void onPreviewTextureCopied() { - } - - @Override - public void onCaptureTextureCopied() { - } - - @Override - public boolean updateStorageHintOnResume() { - return false; - } - - @Override - public void updateCameraAppView() { - } - - @Override - public boolean needsSwitcher() { - return true; - } - - @Override - public boolean needsPieMenu() { - return false; - } - - @Override - public void onShowSwitcherPopup() { - } - - @Override - public void onMediaSaveServiceConnected(MediaSaveService s) { - // do nothing. - } -} |