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