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