Home | History | Annotate | Download | only in camera
      1 /*
      2  * Copyright (C) 2011 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.camera;
     18 
     19 import android.annotation.TargetApi;
     20 import android.content.ContentResolver;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.res.Configuration;
     24 import android.content.res.Resources;
     25 import android.graphics.Bitmap;
     26 import android.graphics.BitmapFactory;
     27 import android.graphics.Canvas;
     28 import android.graphics.ImageFormat;
     29 import android.graphics.Matrix;
     30 import android.graphics.PixelFormat;
     31 import android.graphics.Rect;
     32 import android.graphics.SurfaceTexture;
     33 import android.graphics.YuvImage;
     34 import android.graphics.drawable.BitmapDrawable;
     35 import android.graphics.drawable.Drawable;
     36 import android.hardware.Camera.Parameters;
     37 import android.hardware.Camera.Size;
     38 import android.location.Location;
     39 import android.net.Uri;
     40 import android.os.AsyncTask;
     41 import android.os.Handler;
     42 import android.os.Message;
     43 import android.os.PowerManager;
     44 import android.util.Log;
     45 import android.view.KeyEvent;
     46 import android.view.LayoutInflater;
     47 import android.view.MotionEvent;
     48 import android.view.OrientationEventListener;
     49 import android.view.View;
     50 import android.view.View.OnClickListener;
     51 import android.view.ViewGroup;
     52 import android.view.WindowManager;
     53 import android.widget.ImageView;
     54 import android.widget.LinearLayout;
     55 import android.widget.TextView;
     56 
     57 import com.android.camera.CameraManager.CameraProxy;
     58 import com.android.camera.ui.LayoutChangeNotifier;
     59 import com.android.camera.ui.LayoutNotifyView;
     60 import com.android.camera.ui.PopupManager;
     61 import com.android.camera.ui.Rotatable;
     62 import com.android.gallery3d.R;
     63 import com.android.gallery3d.common.ApiHelper;
     64 import com.android.gallery3d.exif.ExifInterface;
     65 import com.android.gallery3d.exif.ExifTag;
     66 import com.android.gallery3d.ui.GLRootView;
     67 import com.android.gallery3d.util.UsageStatistics;
     68 
     69 import java.io.ByteArrayInputStream;
     70 import java.io.ByteArrayOutputStream;
     71 import java.io.File;
     72 import java.io.FileOutputStream;
     73 import java.io.IOException;
     74 import java.io.InputStream;
     75 import java.util.List;
     76 import java.util.TimeZone;
     77 
     78 /**
     79  * Activity to handle panorama capturing.
     80  */
     81 @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) // uses SurfaceTexture
     82 public class PanoramaModule implements CameraModule,
     83         SurfaceTexture.OnFrameAvailableListener,
     84         ShutterButton.OnShutterButtonListener,
     85         LayoutChangeNotifier.Listener {
     86 
     87     public static final int DEFAULT_SWEEP_ANGLE = 160;
     88     public static final int DEFAULT_BLEND_MODE = Mosaic.BLENDTYPE_HORIZONTAL;
     89     public static final int DEFAULT_CAPTURE_PIXELS = 960 * 720;
     90 
     91     private static final int MSG_LOW_RES_FINAL_MOSAIC_READY = 1;
     92     private static final int MSG_GENERATE_FINAL_MOSAIC_ERROR = 2;
     93     private static final int MSG_END_DIALOG_RESET_TO_PREVIEW = 3;
     94     private static final int MSG_CLEAR_SCREEN_DELAY = 4;
     95     private static final int MSG_CONFIG_MOSAIC_PREVIEW = 5;
     96     private static final int MSG_RESET_TO_PREVIEW = 6;
     97 
     98     private static final int SCREEN_DELAY = 2 * 60 * 1000;
     99 
    100     private static final String TAG = "CAM PanoModule";
    101     private static final int PREVIEW_STOPPED = 0;
    102     private static final int PREVIEW_ACTIVE = 1;
    103     private static final int CAPTURE_STATE_VIEWFINDER = 0;
    104     private static final int CAPTURE_STATE_MOSAIC = 1;
    105     // The unit of speed is degrees per frame.
    106     private static final float PANNING_SPEED_THRESHOLD = 2.5f;
    107 
    108     private ContentResolver mContentResolver;
    109 
    110     private GLRootView mGLRootView;
    111     private ViewGroup mPanoLayout;
    112     private LinearLayout mCaptureLayout;
    113     private View mReviewLayout;
    114     private ImageView mReview;
    115     private View mCaptureIndicator;
    116     private PanoProgressBar mPanoProgressBar;
    117     private PanoProgressBar mSavingProgressBar;
    118     private Matrix mProgressDirectionMatrix = new Matrix();
    119     private float[] mProgressAngle = new float[2];
    120     private LayoutNotifyView mPreviewArea;
    121     private View mLeftIndicator;
    122     private View mRightIndicator;
    123     private MosaicPreviewRenderer mMosaicPreviewRenderer;
    124     private Object mRendererLock = new Object();
    125     private TextView mTooFastPrompt;
    126     private ShutterButton mShutterButton;
    127     private Object mWaitObject = new Object();
    128 
    129     private String mPreparePreviewString;
    130     private String mDialogTitle;
    131     private String mDialogOkString;
    132     private String mDialogPanoramaFailedString;
    133     private String mDialogWaitingPreviousString;
    134 
    135     private int mIndicatorColor;
    136     private int mIndicatorColorFast;
    137     private int mReviewBackground;
    138 
    139     private boolean mUsingFrontCamera;
    140     private int mPreviewWidth;
    141     private int mPreviewHeight;
    142     private int mCameraState;
    143     private int mCaptureState;
    144     private PowerManager.WakeLock mPartialWakeLock;
    145     private MosaicFrameProcessor mMosaicFrameProcessor;
    146     private boolean mMosaicFrameProcessorInitialized;
    147     private AsyncTask <Void, Void, Void> mWaitProcessorTask;
    148     private long mTimeTaken;
    149     private Handler mMainHandler;
    150     private SurfaceTexture mCameraTexture;
    151     private boolean mThreadRunning;
    152     private boolean mCancelComputation;
    153     private float mHorizontalViewAngle;
    154     private float mVerticalViewAngle;
    155 
    156     // Prefer FOCUS_MODE_INFINITY to FOCUS_MODE_CONTINUOUS_VIDEO because of
    157     // getting a better image quality by the former.
    158     private String mTargetFocusMode = Parameters.FOCUS_MODE_INFINITY;
    159 
    160     private PanoOrientationEventListener mOrientationEventListener;
    161     // The value could be 0, 90, 180, 270 for the 4 different orientations measured in clockwise
    162     // respectively.
    163     private int mDeviceOrientation;
    164     private int mDeviceOrientationAtCapture;
    165     private int mCameraOrientation;
    166     private int mOrientationCompensation;
    167 
    168     private RotateDialogController mRotateDialog;
    169 
    170     private SoundClips.Player mSoundPlayer;
    171 
    172     private Runnable mOnFrameAvailableRunnable;
    173 
    174     private CameraActivity mActivity;
    175     private View mRootView;
    176     private CameraProxy mCameraDevice;
    177     private boolean mPaused;
    178     private boolean mIsCreatingRenderer;
    179 
    180     private LocationManager mLocationManager;
    181     private ComboPreferences mPreferences;
    182 
    183     private class MosaicJpeg {
    184         public MosaicJpeg(byte[] data, int width, int height) {
    185             this.data = data;
    186             this.width = width;
    187             this.height = height;
    188             this.isValid = true;
    189         }
    190 
    191         public MosaicJpeg() {
    192             this.data = null;
    193             this.width = 0;
    194             this.height = 0;
    195             this.isValid = false;
    196         }
    197 
    198         public final byte[] data;
    199         public final int width;
    200         public final int height;
    201         public final boolean isValid;
    202     }
    203 
    204     private class PanoOrientationEventListener extends OrientationEventListener {
    205         public PanoOrientationEventListener(Context context) {
    206             super(context);
    207         }
    208 
    209         @Override
    210         public void onOrientationChanged(int orientation) {
    211             // We keep the last known orientation. So if the user first orient
    212             // the camera then point the camera to floor or sky, we still have
    213             // the correct orientation.
    214             if (orientation == ORIENTATION_UNKNOWN) return;
    215             mDeviceOrientation = Util.roundOrientation(orientation, mDeviceOrientation);
    216             // When the screen is unlocked, display rotation may change. Always
    217             // calculate the up-to-date orientationCompensation.
    218             int orientationCompensation = mDeviceOrientation
    219                     + Util.getDisplayRotation(mActivity) % 360;
    220             if (mOrientationCompensation != orientationCompensation) {
    221                 mOrientationCompensation = orientationCompensation;
    222                 mActivity.getGLRoot().requestLayoutContentPane();
    223             }
    224         }
    225     }
    226 
    227     @Override
    228     public void init(CameraActivity activity, View parent, boolean reuseScreenNail) {
    229         mActivity = activity;
    230         mRootView = parent;
    231 
    232         createContentView();
    233 
    234         mContentResolver = mActivity.getContentResolver();
    235         if (reuseScreenNail) {
    236             mActivity.reuseCameraScreenNail(true);
    237         } else {
    238             mActivity.createCameraScreenNail(true);
    239         }
    240 
    241         // This runs in UI thread.
    242         mOnFrameAvailableRunnable = new Runnable() {
    243             @Override
    244             public void run() {
    245                 // Frames might still be available after the activity is paused.
    246                 // If we call onFrameAvailable after pausing, the GL thread will crash.
    247                 if (mPaused) return;
    248 
    249                 MosaicPreviewRenderer renderer = null;
    250                 synchronized (mRendererLock) {
    251                     if (mMosaicPreviewRenderer == null) {
    252                         return;
    253                     }
    254                     renderer = mMosaicPreviewRenderer;
    255                 }
    256                 if (mGLRootView.getVisibility() != View.VISIBLE) {
    257                     renderer.showPreviewFrameSync();
    258                     mGLRootView.setVisibility(View.VISIBLE);
    259                 } else {
    260                     if (mCaptureState == CAPTURE_STATE_VIEWFINDER) {
    261                         renderer.showPreviewFrame();
    262                     } else {
    263                         renderer.alignFrameSync();
    264                         mMosaicFrameProcessor.processFrame();
    265                     }
    266                 }
    267             }
    268         };
    269 
    270         PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE);
    271         mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Panorama");
    272 
    273         mOrientationEventListener = new PanoOrientationEventListener(mActivity);
    274 
    275         mMosaicFrameProcessor = MosaicFrameProcessor.getInstance();
    276 
    277         Resources appRes = mActivity.getResources();
    278         mPreparePreviewString = appRes.getString(R.string.pano_dialog_prepare_preview);
    279         mDialogTitle = appRes.getString(R.string.pano_dialog_title);
    280         mDialogOkString = appRes.getString(R.string.dialog_ok);
    281         mDialogPanoramaFailedString = appRes.getString(R.string.pano_dialog_panorama_failed);
    282         mDialogWaitingPreviousString = appRes.getString(R.string.pano_dialog_waiting_previous);
    283 
    284         mGLRootView = (GLRootView) mActivity.getGLRoot();
    285 
    286         mPreferences = new ComboPreferences(mActivity);
    287         CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
    288         mLocationManager = new LocationManager(mActivity, null);
    289 
    290         mMainHandler = new Handler() {
    291             @Override
    292             public void handleMessage(Message msg) {
    293                 switch (msg.what) {
    294                     case MSG_LOW_RES_FINAL_MOSAIC_READY:
    295                         onBackgroundThreadFinished();
    296                         showFinalMosaic((Bitmap) msg.obj);
    297                         saveHighResMosaic();
    298                         break;
    299                     case MSG_GENERATE_FINAL_MOSAIC_ERROR:
    300                         onBackgroundThreadFinished();
    301                         if (mPaused) {
    302                             resetToPreview();
    303                         } else {
    304                             mRotateDialog.showAlertDialog(
    305                                     mDialogTitle, mDialogPanoramaFailedString,
    306                                     mDialogOkString, new Runnable() {
    307                                         @Override
    308                                         public void run() {
    309                                             resetToPreview();
    310                                         }},
    311                                     null, null);
    312                         }
    313                         clearMosaicFrameProcessorIfNeeded();
    314                         break;
    315                     case MSG_END_DIALOG_RESET_TO_PREVIEW:
    316                         onBackgroundThreadFinished();
    317                         resetToPreview();
    318                         clearMosaicFrameProcessorIfNeeded();
    319                         break;
    320                     case MSG_CLEAR_SCREEN_DELAY:
    321                         mActivity.getWindow().clearFlags(WindowManager.LayoutParams.
    322                                 FLAG_KEEP_SCREEN_ON);
    323                         break;
    324                     case MSG_CONFIG_MOSAIC_PREVIEW:
    325                         configMosaicPreview(msg.arg1, msg.arg2);
    326                         break;
    327                     case MSG_RESET_TO_PREVIEW:
    328                         resetToPreview();
    329                         break;
    330                 }
    331             }
    332         };
    333     }
    334 
    335     @Override
    336     public boolean dispatchTouchEvent(MotionEvent m) {
    337         return mActivity.superDispatchTouchEvent(m);
    338     }
    339 
    340     private void setupCamera() throws CameraHardwareException, CameraDisabledException {
    341         openCamera();
    342         Parameters parameters = mCameraDevice.getParameters();
    343         setupCaptureParams(parameters);
    344         configureCamera(parameters);
    345     }
    346 
    347     private void releaseCamera() {
    348         if (mCameraDevice != null) {
    349             mCameraDevice.setPreviewCallbackWithBuffer(null);
    350             CameraHolder.instance().release();
    351             mCameraDevice = null;
    352             mCameraState = PREVIEW_STOPPED;
    353         }
    354     }
    355 
    356     private void openCamera() throws CameraHardwareException, CameraDisabledException {
    357         int cameraId = CameraHolder.instance().getBackCameraId();
    358         // If there is no back camera, use the first camera. Camera id starts
    359         // from 0. Currently if a camera is not back facing, it is front facing.
    360         // This is also forward compatible if we have a new facing other than
    361         // back or front in the future.
    362         if (cameraId == -1) cameraId = 0;
    363         mCameraDevice = Util.openCamera(mActivity, cameraId);
    364         mCameraOrientation = Util.getCameraOrientation(cameraId);
    365         if (cameraId == CameraHolder.instance().getFrontCameraId()) mUsingFrontCamera = true;
    366     }
    367 
    368     private boolean findBestPreviewSize(List<Size> supportedSizes, boolean need4To3,
    369             boolean needSmaller) {
    370         int pixelsDiff = DEFAULT_CAPTURE_PIXELS;
    371         boolean hasFound = false;
    372         for (Size size : supportedSizes) {
    373             int h = size.height;
    374             int w = size.width;
    375             // we only want 4:3 format.
    376             int d = DEFAULT_CAPTURE_PIXELS - h * w;
    377             if (needSmaller && d < 0) { // no bigger preview than 960x720.
    378                 continue;
    379             }
    380             if (need4To3 && (h * 4 != w * 3)) {
    381                 continue;
    382             }
    383             d = Math.abs(d);
    384             if (d < pixelsDiff) {
    385                 mPreviewWidth = w;
    386                 mPreviewHeight = h;
    387                 pixelsDiff = d;
    388                 hasFound = true;
    389             }
    390         }
    391         return hasFound;
    392     }
    393 
    394     private void setupCaptureParams(Parameters parameters) {
    395         List<Size> supportedSizes = parameters.getSupportedPreviewSizes();
    396         if (!findBestPreviewSize(supportedSizes, true, true)) {
    397             Log.w(TAG, "No 4:3 ratio preview size supported.");
    398             if (!findBestPreviewSize(supportedSizes, false, true)) {
    399                 Log.w(TAG, "Can't find a supported preview size smaller than 960x720.");
    400                 findBestPreviewSize(supportedSizes, false, false);
    401             }
    402         }
    403         Log.v(TAG, "preview h = " + mPreviewHeight + " , w = " + mPreviewWidth);
    404         parameters.setPreviewSize(mPreviewWidth, mPreviewHeight);
    405 
    406         List<int[]> frameRates = parameters.getSupportedPreviewFpsRange();
    407         int last = frameRates.size() - 1;
    408         int minFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MIN_INDEX];
    409         int maxFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MAX_INDEX];
    410         parameters.setPreviewFpsRange(minFps, maxFps);
    411         Log.v(TAG, "preview fps: " + minFps + ", " + maxFps);
    412 
    413         List<String> supportedFocusModes = parameters.getSupportedFocusModes();
    414         if (supportedFocusModes.indexOf(mTargetFocusMode) >= 0) {
    415             parameters.setFocusMode(mTargetFocusMode);
    416         } else {
    417             // Use the default focus mode and log a message
    418             Log.w(TAG, "Cannot set the focus mode to " + mTargetFocusMode +
    419                   " becuase the mode is not supported.");
    420         }
    421 
    422         parameters.set(Util.RECORDING_HINT, Util.FALSE);
    423 
    424         mHorizontalViewAngle = parameters.getHorizontalViewAngle();
    425         mVerticalViewAngle =  parameters.getVerticalViewAngle();
    426     }
    427 
    428     public int getPreviewBufSize() {
    429         PixelFormat pixelInfo = new PixelFormat();
    430         PixelFormat.getPixelFormatInfo(mCameraDevice.getParameters().getPreviewFormat(), pixelInfo);
    431         // TODO: remove this extra 32 byte after the driver bug is fixed.
    432         return (mPreviewWidth * mPreviewHeight * pixelInfo.bitsPerPixel / 8) + 32;
    433     }
    434 
    435     private void configureCamera(Parameters parameters) {
    436         mCameraDevice.setParameters(parameters);
    437     }
    438 
    439     private void configMosaicPreview(final int w, final int h) {
    440         synchronized (mRendererLock) {
    441             if (mIsCreatingRenderer) {
    442                 mMainHandler.removeMessages(MSG_CONFIG_MOSAIC_PREVIEW);
    443                 mMainHandler.obtainMessage(MSG_CONFIG_MOSAIC_PREVIEW, w, h).sendToTarget();
    444                 return;
    445             }
    446             mIsCreatingRenderer = true;
    447         }
    448         stopCameraPreview();
    449         CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail;
    450         screenNail.setSize(w, h);
    451         synchronized (mRendererLock) {
    452             if (mMosaicPreviewRenderer != null) {
    453                 mMosaicPreviewRenderer.release();
    454             }
    455             mMosaicPreviewRenderer = null;
    456             screenNail.releaseSurfaceTexture();
    457             screenNail.acquireSurfaceTexture();
    458         }
    459         mActivity.notifyScreenNailChanged();
    460         final boolean isLandscape = (mActivity.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE);
    461         new Thread(new Runnable() {
    462             @Override
    463             public void run() {
    464                 CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail;
    465                 SurfaceTexture surfaceTexture = screenNail.getSurfaceTexture();
    466                 if (surfaceTexture == null) {
    467                     synchronized (mRendererLock) {
    468                         mIsCreatingRenderer = false;
    469                         mRendererLock.notifyAll();
    470                         return;
    471                     }
    472                 }
    473                 MosaicPreviewRenderer renderer = new MosaicPreviewRenderer(
    474                         screenNail.getSurfaceTexture(), w, h, isLandscape);
    475                 synchronized (mRendererLock) {
    476                     mMosaicPreviewRenderer = renderer;
    477                     mCameraTexture = mMosaicPreviewRenderer.getInputSurfaceTexture();
    478 
    479                     if (!mPaused && !mThreadRunning && mWaitProcessorTask == null) {
    480                         mMainHandler.sendEmptyMessage(MSG_RESET_TO_PREVIEW);
    481                     }
    482                     mIsCreatingRenderer = false;
    483                     mRendererLock.notifyAll();
    484                 }
    485             }
    486         }).start();
    487     }
    488 
    489     // Receives the layout change event from the preview area. So we can set
    490     // the camera preview screennail to the same size and initialize the mosaic
    491     // preview renderer.
    492     @Override
    493     public void onLayoutChange(View v, int l, int t, int r, int b) {
    494         Log.i(TAG, "layout change: "+(r - l) + "/" +(b - t));
    495         mActivity.onLayoutChange(v, l, t, r, b);
    496         configMosaicPreview(r - l, b - t);
    497     }
    498 
    499     @Override
    500     public void onFrameAvailable(SurfaceTexture surface) {
    501         /* This function may be called by some random thread,
    502          * so let's be safe and jump back to ui thread.
    503          * No OpenGL calls can be done here. */
    504         mActivity.runOnUiThread(mOnFrameAvailableRunnable);
    505     }
    506 
    507     private void hideDirectionIndicators() {
    508         mLeftIndicator.setVisibility(View.GONE);
    509         mRightIndicator.setVisibility(View.GONE);
    510     }
    511 
    512     private void showDirectionIndicators(int direction) {
    513         switch (direction) {
    514             case PanoProgressBar.DIRECTION_NONE:
    515                 mLeftIndicator.setVisibility(View.VISIBLE);
    516                 mRightIndicator.setVisibility(View.VISIBLE);
    517                 break;
    518             case PanoProgressBar.DIRECTION_LEFT:
    519                 mLeftIndicator.setVisibility(View.VISIBLE);
    520                 mRightIndicator.setVisibility(View.GONE);
    521                 break;
    522             case PanoProgressBar.DIRECTION_RIGHT:
    523                 mLeftIndicator.setVisibility(View.GONE);
    524                 mRightIndicator.setVisibility(View.VISIBLE);
    525                 break;
    526         }
    527     }
    528 
    529     public void startCapture() {
    530         // Reset values so we can do this again.
    531         mCancelComputation = false;
    532         mTimeTaken = System.currentTimeMillis();
    533         mActivity.setSwipingEnabled(false);
    534         mActivity.hideSwitcher();
    535         mShutterButton.setImageResource(R.drawable.btn_shutter_recording);
    536         mCaptureState = CAPTURE_STATE_MOSAIC;
    537         mCaptureIndicator.setVisibility(View.VISIBLE);
    538         showDirectionIndicators(PanoProgressBar.DIRECTION_NONE);
    539 
    540         mMosaicFrameProcessor.setProgressListener(new MosaicFrameProcessor.ProgressListener() {
    541             @Override
    542             public void onProgress(boolean isFinished, float panningRateX, float panningRateY,
    543                     float progressX, float progressY) {
    544                 float accumulatedHorizontalAngle = progressX * mHorizontalViewAngle;
    545                 float accumulatedVerticalAngle = progressY * mVerticalViewAngle;
    546                 if (isFinished
    547                         || (Math.abs(accumulatedHorizontalAngle) >= DEFAULT_SWEEP_ANGLE)
    548                         || (Math.abs(accumulatedVerticalAngle) >= DEFAULT_SWEEP_ANGLE)) {
    549                     stopCapture(false);
    550                 } else {
    551                     float panningRateXInDegree = panningRateX * mHorizontalViewAngle;
    552                     float panningRateYInDegree = panningRateY * mVerticalViewAngle;
    553                     updateProgress(panningRateXInDegree, panningRateYInDegree,
    554                             accumulatedHorizontalAngle, accumulatedVerticalAngle);
    555                 }
    556             }
    557         });
    558 
    559         mPanoProgressBar.reset();
    560         // TODO: calculate the indicator width according to different devices to reflect the actual
    561         // angle of view of the camera device.
    562         mPanoProgressBar.setIndicatorWidth(20);
    563         mPanoProgressBar.setMaxProgress(DEFAULT_SWEEP_ANGLE);
    564         mPanoProgressBar.setVisibility(View.VISIBLE);
    565         mDeviceOrientationAtCapture = mDeviceOrientation;
    566         keepScreenOn();
    567         mActivity.getOrientationManager().lockOrientation();
    568         setupProgressDirectionMatrix();
    569     }
    570 
    571     void setupProgressDirectionMatrix() {
    572         int degrees = Util.getDisplayRotation(mActivity);
    573         int cameraId = CameraHolder.instance().getBackCameraId();
    574         int orientation = Util.getDisplayOrientation(degrees, cameraId);
    575         mProgressDirectionMatrix.reset();
    576         mProgressDirectionMatrix.postRotate(orientation);
    577     }
    578 
    579     private void stopCapture(boolean aborted) {
    580         mCaptureState = CAPTURE_STATE_VIEWFINDER;
    581         mCaptureIndicator.setVisibility(View.GONE);
    582         hideTooFastIndication();
    583         hideDirectionIndicators();
    584 
    585         mMosaicFrameProcessor.setProgressListener(null);
    586         stopCameraPreview();
    587 
    588         mCameraTexture.setOnFrameAvailableListener(null);
    589 
    590         if (!aborted && !mThreadRunning) {
    591             mRotateDialog.showWaitingDialog(mPreparePreviewString);
    592             // Hide shutter button, shutter icon, etc when waiting for
    593             // panorama to stitch
    594             mActivity.hideUI();
    595             runBackgroundThread(new Thread() {
    596                 @Override
    597                 public void run() {
    598                     MosaicJpeg jpeg = generateFinalMosaic(false);
    599 
    600                     if (jpeg != null && jpeg.isValid) {
    601                         Bitmap bitmap = null;
    602                         bitmap = BitmapFactory.decodeByteArray(jpeg.data, 0, jpeg.data.length);
    603                         mMainHandler.sendMessage(mMainHandler.obtainMessage(
    604                                 MSG_LOW_RES_FINAL_MOSAIC_READY, bitmap));
    605                     } else {
    606                         mMainHandler.sendMessage(mMainHandler.obtainMessage(
    607                                 MSG_END_DIALOG_RESET_TO_PREVIEW));
    608                     }
    609                 }
    610             });
    611         }
    612         keepScreenOnAwhile();
    613     }
    614 
    615     private void showTooFastIndication() {
    616         mTooFastPrompt.setVisibility(View.VISIBLE);
    617         // The PreviewArea also contains the border for "too fast" indication.
    618         mPreviewArea.setVisibility(View.VISIBLE);
    619         mPanoProgressBar.setIndicatorColor(mIndicatorColorFast);
    620         mLeftIndicator.setEnabled(true);
    621         mRightIndicator.setEnabled(true);
    622     }
    623 
    624     private void hideTooFastIndication() {
    625         mTooFastPrompt.setVisibility(View.GONE);
    626         // We set "INVISIBLE" instead of "GONE" here because we need mPreviewArea to have layout
    627         // information so we can know the size and position for mCameraScreenNail.
    628         mPreviewArea.setVisibility(View.INVISIBLE);
    629         mPanoProgressBar.setIndicatorColor(mIndicatorColor);
    630         mLeftIndicator.setEnabled(false);
    631         mRightIndicator.setEnabled(false);
    632     }
    633 
    634     private void updateProgress(float panningRateXInDegree, float panningRateYInDegree,
    635             float progressHorizontalAngle, float progressVerticalAngle) {
    636         mGLRootView.requestRender();
    637 
    638         if ((Math.abs(panningRateXInDegree) > PANNING_SPEED_THRESHOLD)
    639             || (Math.abs(panningRateYInDegree) > PANNING_SPEED_THRESHOLD)) {
    640             showTooFastIndication();
    641         } else {
    642             hideTooFastIndication();
    643         }
    644 
    645         // progressHorizontalAngle and progressVerticalAngle are relative to the
    646         // camera. Convert them to UI direction.
    647         mProgressAngle[0] = progressHorizontalAngle;
    648         mProgressAngle[1] = progressVerticalAngle;
    649         mProgressDirectionMatrix.mapPoints(mProgressAngle);
    650 
    651         int angleInMajorDirection =
    652                 (Math.abs(mProgressAngle[0]) > Math.abs(mProgressAngle[1]))
    653                 ? (int) mProgressAngle[0]
    654                 : (int) mProgressAngle[1];
    655         mPanoProgressBar.setProgress((angleInMajorDirection));
    656     }
    657 
    658     private void setViews(Resources appRes) {
    659         mCaptureState = CAPTURE_STATE_VIEWFINDER;
    660         mPanoProgressBar = (PanoProgressBar) mRootView.findViewById(R.id.pano_pan_progress_bar);
    661         mPanoProgressBar.setBackgroundColor(appRes.getColor(R.color.pano_progress_empty));
    662         mPanoProgressBar.setDoneColor(appRes.getColor(R.color.pano_progress_done));
    663         mPanoProgressBar.setIndicatorColor(mIndicatorColor);
    664         mPanoProgressBar.setOnDirectionChangeListener(
    665                 new PanoProgressBar.OnDirectionChangeListener () {
    666                     @Override
    667                     public void onDirectionChange(int direction) {
    668                         if (mCaptureState == CAPTURE_STATE_MOSAIC) {
    669                             showDirectionIndicators(direction);
    670                         }
    671                     }
    672                 });
    673 
    674         mLeftIndicator = mRootView.findViewById(R.id.pano_pan_left_indicator);
    675         mRightIndicator = mRootView.findViewById(R.id.pano_pan_right_indicator);
    676         mLeftIndicator.setEnabled(false);
    677         mRightIndicator.setEnabled(false);
    678         mTooFastPrompt = (TextView) mRootView.findViewById(R.id.pano_capture_too_fast_textview);
    679         // This mPreviewArea also shows the border for visual "too fast" indication.
    680         mPreviewArea = (LayoutNotifyView) mRootView.findViewById(R.id.pano_preview_area);
    681         mPreviewArea.setOnLayoutChangeListener(this);
    682 
    683         mSavingProgressBar = (PanoProgressBar) mRootView.findViewById(R.id.pano_saving_progress_bar);
    684         mSavingProgressBar.setIndicatorWidth(0);
    685         mSavingProgressBar.setMaxProgress(100);
    686         mSavingProgressBar.setBackgroundColor(appRes.getColor(R.color.pano_progress_empty));
    687         mSavingProgressBar.setDoneColor(appRes.getColor(R.color.pano_progress_indication));
    688 
    689         mCaptureIndicator = mRootView.findViewById(R.id.pano_capture_indicator);
    690 
    691         mReviewLayout = mRootView.findViewById(R.id.pano_review_layout);
    692         mReview = (ImageView) mRootView.findViewById(R.id.pano_reviewarea);
    693         mReview.setBackgroundColor(mReviewBackground);
    694         View cancelButton = mRootView.findViewById(R.id.pano_review_cancel_button);
    695         cancelButton.setOnClickListener(new OnClickListener() {
    696             @Override
    697             public void onClick(View arg0) {
    698                 if (mPaused || mCameraTexture == null) return;
    699                 cancelHighResComputation();
    700             }
    701         });
    702 
    703         mShutterButton = mActivity.getShutterButton();
    704         mShutterButton.setImageResource(R.drawable.btn_new_shutter);
    705         mShutterButton.setOnShutterButtonListener(this);
    706 
    707         if (mActivity.getResources().getConfiguration().orientation
    708                 == Configuration.ORIENTATION_PORTRAIT) {
    709             Rotatable view = (Rotatable) mRootView.findViewById(R.id.pano_rotate_reviewarea);
    710             view.setOrientation(270, false);
    711         }
    712     }
    713 
    714     private void createContentView() {
    715         mActivity.getLayoutInflater().inflate(R.layout.panorama_module, (ViewGroup) mRootView, true);
    716         Resources appRes = mActivity.getResources();
    717         mCaptureLayout = (LinearLayout) mRootView.findViewById(R.id.camera_app);
    718         mIndicatorColor = appRes.getColor(R.color.pano_progress_indication);
    719         mReviewBackground = appRes.getColor(R.color.review_background);
    720         mIndicatorColorFast = appRes.getColor(R.color.pano_progress_indication_fast);
    721         mPanoLayout = (ViewGroup) mRootView.findViewById(R.id.camera_app_root);
    722         mRotateDialog = new RotateDialogController(mActivity, R.layout.rotate_dialog);
    723         setViews(appRes);
    724     }
    725 
    726     @Override
    727     public void onShutterButtonClick() {
    728         // If mCameraTexture == null then GL setup is not finished yet.
    729         // No buttons can be pressed.
    730         if (mPaused || mThreadRunning || mCameraTexture == null) return;
    731         // Since this button will stay on the screen when capturing, we need to check the state
    732         // right now.
    733         switch (mCaptureState) {
    734             case CAPTURE_STATE_VIEWFINDER:
    735                 if(mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) return;
    736                 mSoundPlayer.play(SoundClips.START_VIDEO_RECORDING);
    737                 startCapture();
    738                 break;
    739             case CAPTURE_STATE_MOSAIC:
    740                 mSoundPlayer.play(SoundClips.STOP_VIDEO_RECORDING);
    741                 stopCapture(false);
    742         }
    743     }
    744 
    745     @Override
    746     public void onShutterButtonFocus(boolean pressed) {
    747     }
    748 
    749     public void reportProgress() {
    750         mSavingProgressBar.reset();
    751         mSavingProgressBar.setRightIncreasing(true);
    752         Thread t = new Thread() {
    753             @Override
    754             public void run() {
    755                 while (mThreadRunning) {
    756                     final int progress = mMosaicFrameProcessor.reportProgress(
    757                             true, mCancelComputation);
    758 
    759                     try {
    760                         synchronized (mWaitObject) {
    761                             mWaitObject.wait(50);
    762                         }
    763                     } catch (InterruptedException e) {
    764                         throw new RuntimeException("Panorama reportProgress failed", e);
    765                     }
    766                     // Update the progress bar
    767                     mActivity.runOnUiThread(new Runnable() {
    768                         @Override
    769                         public void run() {
    770                             mSavingProgressBar.setProgress(progress);
    771                         }
    772                     });
    773                 }
    774             }
    775         };
    776         t.start();
    777     }
    778 
    779     private int getCaptureOrientation() {
    780         // The panorama image returned from the library is oriented based on the
    781         // natural orientation of a camera. We need to set an orientation for the image
    782         // in its EXIF header, so the image can be displayed correctly.
    783         // The orientation is calculated from compensating the
    784         // device orientation at capture and the camera orientation respective to
    785         // the natural orientation of the device.
    786         int orientation;
    787         if (mUsingFrontCamera) {
    788             // mCameraOrientation is negative with respect to the front facing camera.
    789             // See document of android.hardware.Camera.Parameters.setRotation.
    790             orientation = (mDeviceOrientationAtCapture - mCameraOrientation + 360) % 360;
    791         } else {
    792             orientation = (mDeviceOrientationAtCapture + mCameraOrientation) % 360;
    793         }
    794         return orientation;
    795     }
    796 
    797     public void saveHighResMosaic() {
    798         runBackgroundThread(new Thread() {
    799             @Override
    800             public void run() {
    801                 mPartialWakeLock.acquire();
    802                 MosaicJpeg jpeg;
    803                 try {
    804                     jpeg = generateFinalMosaic(true);
    805                 } finally {
    806                     mPartialWakeLock.release();
    807                 }
    808 
    809                 if (jpeg == null) {  // Cancelled by user.
    810                     mMainHandler.sendEmptyMessage(MSG_END_DIALOG_RESET_TO_PREVIEW);
    811                 } else if (!jpeg.isValid) {  // Error when generating mosaic.
    812                     mMainHandler.sendEmptyMessage(MSG_GENERATE_FINAL_MOSAIC_ERROR);
    813                 } else {
    814                     int orientation = getCaptureOrientation();
    815                     Uri uri = savePanorama(jpeg.data, jpeg.width, jpeg.height, orientation);
    816                     if (uri != null) {
    817                         mActivity.addSecureAlbumItemIfNeeded(false, uri);
    818                         Util.broadcastNewPicture(mActivity, uri);
    819                     }
    820                     mMainHandler.sendMessage(
    821                             mMainHandler.obtainMessage(MSG_END_DIALOG_RESET_TO_PREVIEW));
    822                 }
    823             }
    824         });
    825         reportProgress();
    826     }
    827 
    828     private void runBackgroundThread(Thread thread) {
    829         mThreadRunning = true;
    830         thread.start();
    831     }
    832 
    833     private void onBackgroundThreadFinished() {
    834         mThreadRunning = false;
    835         mRotateDialog.dismissDialog();
    836     }
    837 
    838     private void cancelHighResComputation() {
    839         mCancelComputation = true;
    840         synchronized (mWaitObject) {
    841             mWaitObject.notify();
    842         }
    843     }
    844 
    845     // This function will be called upon the first camera frame is available.
    846     private void reset() {
    847         mCaptureState = CAPTURE_STATE_VIEWFINDER;
    848 
    849         mActivity.getOrientationManager().unlockOrientation();
    850         // We should set mGLRootView visible too. However, since there might be no
    851         // frame available yet, setting mGLRootView visible should be done right after
    852         // the first camera frame is available and therefore it is done by
    853         // mOnFirstFrameAvailableRunnable.
    854         mActivity.setSwipingEnabled(true);
    855         mShutterButton.setImageResource(R.drawable.btn_new_shutter);
    856         mReviewLayout.setVisibility(View.GONE);
    857         mPanoProgressBar.setVisibility(View.GONE);
    858         mGLRootView.setVisibility(View.VISIBLE);
    859         // Orientation change will trigger onLayoutChange->configMosaicPreview->
    860         // resetToPreview. Do not show the capture UI in film strip.
    861         if (mActivity.mShowCameraAppView) {
    862             mCaptureLayout.setVisibility(View.VISIBLE);
    863             mActivity.showUI();
    864         }
    865         mMosaicFrameProcessor.reset();
    866     }
    867 
    868     private void resetToPreview() {
    869         reset();
    870         if (!mPaused) startCameraPreview();
    871     }
    872 
    873     private static class FlipBitmapDrawable extends BitmapDrawable {
    874 
    875         public FlipBitmapDrawable(Resources res, Bitmap bitmap) {
    876             super(res, bitmap);
    877         }
    878 
    879         @Override
    880         public void draw(Canvas canvas) {
    881             Rect bounds = getBounds();
    882             int cx = bounds.centerX();
    883             int cy = bounds.centerY();
    884             canvas.save(Canvas.MATRIX_SAVE_FLAG);
    885             canvas.rotate(180, cx, cy);
    886             super.draw(canvas);
    887             canvas.restore();
    888         }
    889     }
    890 
    891     private void showFinalMosaic(Bitmap bitmap) {
    892         if (bitmap != null) {
    893             int orientation = getCaptureOrientation();
    894             if (orientation >= 180) {
    895                 // We need to flip the drawable to compensate
    896                 mReview.setImageDrawable(new FlipBitmapDrawable(
    897                         mActivity.getResources(), bitmap));
    898             } else {
    899                 mReview.setImageBitmap(bitmap);
    900             }
    901         }
    902 
    903         mCaptureLayout.setVisibility(View.GONE);
    904         mReviewLayout.setVisibility(View.VISIBLE);
    905     }
    906 
    907     private Uri savePanorama(byte[] jpegData, int width, int height, int orientation) {
    908         if (jpegData != null) {
    909             String filename = PanoUtil.createName(
    910                     mActivity.getResources().getString(R.string.pano_file_name_format), mTimeTaken);
    911             String filepath = Storage.generateFilepath(filename);
    912 
    913             Location loc = mLocationManager.getCurrentLocation();
    914             ExifInterface exif = new ExifInterface();
    915             try {
    916                 exif.readExif(jpegData);
    917                 exif.addGpsDateTimeStampTag(mTimeTaken);
    918                 exif.addDateTimeStampTag(ExifInterface.TAG_DATE_TIME, mTimeTaken,
    919                         TimeZone.getDefault());
    920                 exif.setTag(exif.buildTag(ExifInterface.TAG_ORIENTATION,
    921                         ExifInterface.getOrientationValueForRotation(orientation)));
    922                 writeLocation(loc, exif);
    923                 exif.writeExif(jpegData, filepath);
    924             } catch (IOException e) {
    925                 Log.e(TAG, "Cannot set exif for " + filepath, e);
    926                 Storage.writeFile(filepath, jpegData);
    927             }
    928             int jpegLength = (int) (new File(filepath).length());
    929             return Storage.addImage(mContentResolver, filename, mTimeTaken,
    930                     loc, orientation, jpegLength, filepath, width, height);
    931         }
    932         return null;
    933     }
    934 
    935     private static void writeLocation(Location location, ExifInterface exif) {
    936         if (location == null) {
    937             return;
    938         }
    939         exif.addGpsTags(location.getLatitude(), location.getLongitude());
    940         exif.setTag(exif.buildTag(ExifInterface.TAG_GPS_PROCESSING_METHOD, location.getProvider()));
    941     }
    942 
    943     private void clearMosaicFrameProcessorIfNeeded() {
    944         if (!mPaused || mThreadRunning) return;
    945         // Only clear the processor if it is initialized by this activity
    946         // instance. Other activity instances may be using it.
    947         if (mMosaicFrameProcessorInitialized) {
    948             mMosaicFrameProcessor.clear();
    949             mMosaicFrameProcessorInitialized = false;
    950         }
    951     }
    952 
    953     private void initMosaicFrameProcessorIfNeeded() {
    954         if (mPaused || mThreadRunning) return;
    955         mMosaicFrameProcessor.initialize(
    956                 mPreviewWidth, mPreviewHeight, getPreviewBufSize());
    957         mMosaicFrameProcessorInitialized = true;
    958     }
    959 
    960     @Override
    961     public void onPauseBeforeSuper() {
    962         mPaused = true;
    963         if (mLocationManager != null) mLocationManager.recordLocation(false);
    964     }
    965 
    966     @Override
    967     public void onPauseAfterSuper() {
    968         mOrientationEventListener.disable();
    969         if (mCameraDevice == null) {
    970             // Camera open failed. Nothing should be done here.
    971             return;
    972         }
    973         // Stop the capturing first.
    974         if (mCaptureState == CAPTURE_STATE_MOSAIC) {
    975             stopCapture(true);
    976             reset();
    977         }
    978 
    979         releaseCamera();
    980         synchronized (mRendererLock) {
    981             mCameraTexture = null;
    982 
    983             // The preview renderer might not have a chance to be initialized
    984             // before onPause().
    985             if (mMosaicPreviewRenderer != null) {
    986                 mMosaicPreviewRenderer.release();
    987                 mMosaicPreviewRenderer = null;
    988             }
    989         }
    990 
    991         clearMosaicFrameProcessorIfNeeded();
    992         if (mWaitProcessorTask != null) {
    993             mWaitProcessorTask.cancel(true);
    994             mWaitProcessorTask = null;
    995         }
    996         resetScreenOn();
    997         if (mSoundPlayer != null) {
    998             mSoundPlayer.release();
    999             mSoundPlayer = null;
   1000         }
   1001         CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail;
   1002         screenNail.releaseSurfaceTexture();
   1003         System.gc();
   1004     }
   1005 
   1006     @Override
   1007     public void onConfigurationChanged(Configuration newConfig) {
   1008         Drawable lowResReview = null;
   1009         if (mThreadRunning) lowResReview = mReview.getDrawable();
   1010 
   1011         // Change layout in response to configuration change
   1012         mCaptureLayout.setOrientation(
   1013                 newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
   1014                 ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
   1015         mCaptureLayout.removeAllViews();
   1016         LayoutInflater inflater = mActivity.getLayoutInflater();
   1017         inflater.inflate(R.layout.preview_frame_pano, mCaptureLayout);
   1018 
   1019         mPanoLayout.removeView(mReviewLayout);
   1020         inflater.inflate(R.layout.pano_review, mPanoLayout);
   1021 
   1022         setViews(mActivity.getResources());
   1023         if (mThreadRunning) {
   1024             mReview.setImageDrawable(lowResReview);
   1025             mCaptureLayout.setVisibility(View.GONE);
   1026             mReviewLayout.setVisibility(View.VISIBLE);
   1027         }
   1028     }
   1029 
   1030     @Override
   1031     public void onOrientationChanged(int orientation) {
   1032     }
   1033 
   1034     @Override
   1035     public void onResumeBeforeSuper() {
   1036         mPaused = false;
   1037     }
   1038 
   1039     @Override
   1040     public void onResumeAfterSuper() {
   1041         mOrientationEventListener.enable();
   1042 
   1043         mCaptureState = CAPTURE_STATE_VIEWFINDER;
   1044 
   1045         try {
   1046             setupCamera();
   1047         } catch (CameraHardwareException e) {
   1048             Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
   1049             return;
   1050         } catch (CameraDisabledException e) {
   1051             Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
   1052             return;
   1053         }
   1054 
   1055         // Set up sound playback for shutter button
   1056         mSoundPlayer = SoundClips.getPlayer(mActivity);
   1057 
   1058         // Check if another panorama instance is using the mosaic frame processor.
   1059         mRotateDialog.dismissDialog();
   1060         if (!mThreadRunning && mMosaicFrameProcessor.isMosaicMemoryAllocated()) {
   1061             mGLRootView.setVisibility(View.GONE);
   1062             mRotateDialog.showWaitingDialog(mDialogWaitingPreviousString);
   1063             // If stitching is still going on, make sure switcher and shutter button
   1064             // are not showing
   1065             mActivity.hideUI();
   1066             mWaitProcessorTask = new WaitProcessorTask().execute();
   1067         } else {
   1068             mGLRootView.setVisibility(View.VISIBLE);
   1069             // Camera must be initialized before MosaicFrameProcessor is
   1070             // initialized. The preview size has to be decided by camera device.
   1071             initMosaicFrameProcessorIfNeeded();
   1072             int w = mPreviewArea.getWidth();
   1073             int h = mPreviewArea.getHeight();
   1074             if (w != 0 && h != 0) {  // The layout has been calculated.
   1075                 configMosaicPreview(w, h);
   1076             }
   1077         }
   1078         keepScreenOnAwhile();
   1079 
   1080         // Initialize location service.
   1081         boolean recordLocation = RecordLocationPreference.get(mPreferences,
   1082                 mContentResolver);
   1083         mLocationManager.recordLocation(recordLocation);
   1084 
   1085         // Dismiss open menu if exists.
   1086         PopupManager.getInstance(mActivity).notifyShowPopup(null);
   1087         mRootView.requestLayout();
   1088         UsageStatistics.onContentViewChanged(
   1089                 UsageStatistics.COMPONENT_CAMERA, "PanoramaModule");
   1090     }
   1091 
   1092     /**
   1093      * Generate the final mosaic image.
   1094      *
   1095      * @param highRes flag to indicate whether we want to get a high-res version.
   1096      * @return a MosaicJpeg with its isValid flag set to true if successful; null if the generation
   1097      *         process is cancelled; and a MosaicJpeg with its isValid flag set to false if there
   1098      *         is an error in generating the final mosaic.
   1099      */
   1100     public MosaicJpeg generateFinalMosaic(boolean highRes) {
   1101         int mosaicReturnCode = mMosaicFrameProcessor.createMosaic(highRes);
   1102         if (mosaicReturnCode == Mosaic.MOSAIC_RET_CANCELLED) {
   1103             return null;
   1104         } else if (mosaicReturnCode == Mosaic.MOSAIC_RET_ERROR) {
   1105             return new MosaicJpeg();
   1106         }
   1107 
   1108         byte[] imageData = mMosaicFrameProcessor.getFinalMosaicNV21();
   1109         if (imageData == null) {
   1110             Log.e(TAG, "getFinalMosaicNV21() returned null.");
   1111             return new MosaicJpeg();
   1112         }
   1113 
   1114         int len = imageData.length - 8;
   1115         int width = (imageData[len + 0] << 24) + ((imageData[len + 1] & 0xFF) << 16)
   1116                 + ((imageData[len + 2] & 0xFF) << 8) + (imageData[len + 3] & 0xFF);
   1117         int height = (imageData[len + 4] << 24) + ((imageData[len + 5] & 0xFF) << 16)
   1118                 + ((imageData[len + 6] & 0xFF) << 8) + (imageData[len + 7] & 0xFF);
   1119         Log.v(TAG, "ImLength = " + (len) + ", W = " + width + ", H = " + height);
   1120 
   1121         if (width <= 0 || height <= 0) {
   1122             // TODO: pop up an error message indicating that the final result is not generated.
   1123             Log.e(TAG, "width|height <= 0!!, len = " + (len) + ", W = " + width + ", H = " +
   1124                     height);
   1125             return new MosaicJpeg();
   1126         }
   1127 
   1128         YuvImage yuvimage = new YuvImage(imageData, ImageFormat.NV21, width, height, null);
   1129         ByteArrayOutputStream out = new ByteArrayOutputStream();
   1130         yuvimage.compressToJpeg(new Rect(0, 0, width, height), 100, out);
   1131         try {
   1132             out.close();
   1133         } catch (Exception e) {
   1134             Log.e(TAG, "Exception in storing final mosaic", e);
   1135             return new MosaicJpeg();
   1136         }
   1137         return new MosaicJpeg(out.toByteArray(), width, height);
   1138     }
   1139 
   1140     private void startCameraPreview() {
   1141         if (mCameraDevice == null) {
   1142             // Camera open failed. Return.
   1143             return;
   1144         }
   1145 
   1146         // This works around a driver issue. startPreview may fail if
   1147         // stopPreview/setPreviewTexture/startPreview are called several times
   1148         // in a row. mCameraTexture can be null after pressing home during
   1149         // mosaic generation and coming back. Preview will be started later in
   1150         // onLayoutChange->configMosaicPreview. This also reduces the latency.
   1151         synchronized (mRendererLock) {
   1152             if (mCameraTexture == null) return;
   1153 
   1154             // If we're previewing already, stop the preview first (this will
   1155             // blank the screen).
   1156             if (mCameraState != PREVIEW_STOPPED) stopCameraPreview();
   1157 
   1158             // Set the display orientation to 0, so that the underlying mosaic
   1159             // library can always get undistorted mPreviewWidth x mPreviewHeight
   1160             // image data from SurfaceTexture.
   1161             mCameraDevice.setDisplayOrientation(0);
   1162 
   1163             mCameraTexture.setOnFrameAvailableListener(this);
   1164             mCameraDevice.setPreviewTextureAsync(mCameraTexture);
   1165         }
   1166         mCameraDevice.startPreviewAsync();
   1167         mCameraState = PREVIEW_ACTIVE;
   1168     }
   1169 
   1170     private void stopCameraPreview() {
   1171         if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
   1172             Log.v(TAG, "stopPreview");
   1173             mCameraDevice.stopPreview();
   1174         }
   1175         mCameraState = PREVIEW_STOPPED;
   1176     }
   1177 
   1178     @Override
   1179     public void onUserInteraction() {
   1180         if (mCaptureState != CAPTURE_STATE_MOSAIC) keepScreenOnAwhile();
   1181     }
   1182 
   1183     @Override
   1184     public boolean onBackPressed() {
   1185         // If panorama is generating low res or high res mosaic, ignore back
   1186         // key. So the activity will not be destroyed.
   1187         if (mThreadRunning) return true;
   1188         return false;
   1189     }
   1190 
   1191     private void resetScreenOn() {
   1192         mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY);
   1193         mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
   1194     }
   1195 
   1196     private void keepScreenOnAwhile() {
   1197         mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY);
   1198         mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
   1199         mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_DELAY, SCREEN_DELAY);
   1200     }
   1201 
   1202     private void keepScreenOn() {
   1203         mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY);
   1204         mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
   1205     }
   1206 
   1207     private class WaitProcessorTask extends AsyncTask<Void, Void, Void> {
   1208         @Override
   1209         protected Void doInBackground(Void... params) {
   1210             synchronized (mMosaicFrameProcessor) {
   1211                 while (!isCancelled() && mMosaicFrameProcessor.isMosaicMemoryAllocated()) {
   1212                     try {
   1213                         mMosaicFrameProcessor.wait();
   1214                     } catch (Exception e) {
   1215                         // ignore
   1216                     }
   1217                 }
   1218             }
   1219             return null;
   1220         }
   1221 
   1222         @Override
   1223         protected void onPostExecute(Void result) {
   1224             mWaitProcessorTask = null;
   1225             mRotateDialog.dismissDialog();
   1226             mGLRootView.setVisibility(View.VISIBLE);
   1227             initMosaicFrameProcessorIfNeeded();
   1228             int w = mPreviewArea.getWidth();
   1229             int h = mPreviewArea.getHeight();
   1230             if (w != 0 && h != 0) {  // The layout has been calculated.
   1231                 configMosaicPreview(w, h);
   1232             }
   1233             resetToPreview();
   1234         }
   1235     }
   1236 
   1237     @Override
   1238     public void onFullScreenChanged(boolean full) {
   1239     }
   1240 
   1241 
   1242     @Override
   1243     public void onStop() {
   1244     }
   1245 
   1246     @Override
   1247     public void installIntentFilter() {
   1248     }
   1249 
   1250     @Override
   1251     public void onActivityResult(int requestCode, int resultCode, Intent data) {
   1252     }
   1253 
   1254 
   1255     @Override
   1256     public boolean onKeyDown(int keyCode, KeyEvent event) {
   1257         return false;
   1258     }
   1259 
   1260     @Override
   1261     public boolean onKeyUp(int keyCode, KeyEvent event) {
   1262         return false;
   1263     }
   1264 
   1265     @Override
   1266     public void onSingleTapUp(View view, int x, int y) {
   1267     }
   1268 
   1269     @Override
   1270     public void onPreviewTextureCopied() {
   1271     }
   1272 
   1273     @Override
   1274     public void onCaptureTextureCopied() {
   1275     }
   1276 
   1277     @Override
   1278     public boolean updateStorageHintOnResume() {
   1279         return false;
   1280     }
   1281 
   1282     @Override
   1283     public void updateCameraAppView() {
   1284     }
   1285 
   1286     @Override
   1287     public boolean needsSwitcher() {
   1288         return true;
   1289     }
   1290 
   1291     @Override
   1292     public boolean needsPieMenu() {
   1293         return false;
   1294     }
   1295 
   1296     @Override
   1297     public void onShowSwitcherPopup() {
   1298     }
   1299 
   1300     @Override
   1301     public void onMediaSaveServiceConnected(MediaSaveService s) {
   1302         // do nothing.
   1303     }
   1304 }
   1305